587 lines
13 KiB
C++
587 lines
13 KiB
C++
/*
|
|
Copyright 2023 flyinghead
|
|
|
|
This file is part of Flycast.
|
|
|
|
Flycast is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Flycast 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 Flycast. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#pragma once
|
|
#include "types.h"
|
|
#ifdef NAOMI_MULTIBOARD
|
|
#include "naomi_regs.h"
|
|
#include "hw/holly/sb.h"
|
|
#include "hw/sh4/sh4_mem.h"
|
|
#include <atomic>
|
|
|
|
|
|
#ifdef _WIN32
|
|
#include <winsock2.h>
|
|
#include <windows.h>
|
|
|
|
class IpcMutex
|
|
{
|
|
HANDLE mutex;
|
|
|
|
public:
|
|
using native_handle_type = HANDLE;
|
|
|
|
IpcMutex()
|
|
{
|
|
SECURITY_ATTRIBUTES secattr{ sizeof(SECURITY_ATTRIBUTES) };
|
|
secattr.bInheritHandle = TRUE;
|
|
|
|
mutex = CreateMutex(&secattr, FALSE, NULL);
|
|
if (mutex == NULL)
|
|
throw std::runtime_error("CreateMutex failed");
|
|
}
|
|
IpcMutex(const IpcMutex&) = delete;
|
|
|
|
~IpcMutex() {
|
|
CloseHandle(mutex);
|
|
}
|
|
|
|
void lock() {
|
|
WaitForSingleObject(mutex, INFINITE);
|
|
}
|
|
|
|
void unlock() {
|
|
ReleaseMutex(mutex);
|
|
}
|
|
|
|
native_handle_type native_handle() {
|
|
return mutex;
|
|
}
|
|
|
|
IpcMutex& operator=(const IpcMutex&) = delete;
|
|
};
|
|
|
|
class IpcConditionVariable
|
|
{
|
|
class Semaphore
|
|
{
|
|
HANDLE handle;
|
|
|
|
public:
|
|
Semaphore()
|
|
{
|
|
SECURITY_ATTRIBUTES secattr{ sizeof(SECURITY_ATTRIBUTES) };
|
|
secattr.bInheritHandle = TRUE;
|
|
handle = CreateSemaphore(&secattr, 0, std::numeric_limits<LONG>::max(), NULL);
|
|
if (handle == NULL)
|
|
throw std::runtime_error("Semaphore create failed");
|
|
}
|
|
~Semaphore() {
|
|
CloseHandle(handle);
|
|
}
|
|
|
|
bool wait(DWORD msecs = INFINITE)
|
|
{
|
|
DWORD rc = WaitForSingleObject(handle, msecs);
|
|
if (rc == WAIT_ABANDONED || rc == WAIT_FAILED)
|
|
throw std::runtime_error("Semaphore wait failure");
|
|
return rc != WAIT_TIMEOUT;
|
|
}
|
|
|
|
void signal(int n = 1) {
|
|
ReleaseSemaphore(handle, n, nullptr);
|
|
}
|
|
};
|
|
|
|
Semaphore semaphore;
|
|
IpcMutex mutex;
|
|
int waiters = 0;
|
|
// The unlock/wait/lock in wait() should be atomic so the h semaphore is used
|
|
// to make sure all processes about to wait (i.e. on the waiters list) are notified before any other.
|
|
// This race condition happens more frequently with one slave, where one process
|
|
// notifies the other that everybody is ready and ends up notifying itself,
|
|
// while the other process waits indefinitely.
|
|
// See https://birrell.org/andrew/papers/ImplementingCVs.pdf
|
|
Semaphore h;
|
|
|
|
std::cv_status waitMs(std::unique_lock<IpcMutex>& lock, DWORD msecs)
|
|
{
|
|
mutex.lock();
|
|
waiters++;
|
|
mutex.unlock();
|
|
|
|
lock.unlock();
|
|
std::cv_status status = semaphore.wait(msecs) ?
|
|
std::cv_status::no_timeout : std::cv_status::timeout;
|
|
// must be done before re-acquiring the lock
|
|
h.signal();
|
|
lock.lock();
|
|
return status;
|
|
}
|
|
|
|
public:
|
|
void notify_all()
|
|
{
|
|
mutex.lock();
|
|
if (waiters > 0)
|
|
{
|
|
semaphore.signal(waiters);
|
|
// make sure waiters are notified before moving on
|
|
do {
|
|
h.wait();
|
|
waiters--;
|
|
} while (waiters > 0);
|
|
}
|
|
mutex.unlock();
|
|
}
|
|
|
|
void wait(std::unique_lock<IpcMutex>& lock)
|
|
{
|
|
waitMs(lock, INFINITE);
|
|
}
|
|
|
|
template<class Rep, class Period>
|
|
std::cv_status wait_for(std::unique_lock<IpcMutex>& lock,
|
|
const std::chrono::duration<Rep, Period>& rel_time)
|
|
{
|
|
return waitMs(lock, std::chrono::duration_cast<std::chrono::milliseconds>(rel_time).count());
|
|
}
|
|
|
|
IpcConditionVariable& operator=(const IpcConditionVariable&) = delete;
|
|
};
|
|
|
|
#else // _!WIN32
|
|
|
|
#include <pthread.h>
|
|
|
|
class IpcMutex
|
|
{
|
|
pthread_mutex_t mutex;
|
|
|
|
public:
|
|
using native_handle_type = pthread_mutex_t*;
|
|
|
|
IpcMutex()
|
|
{
|
|
pthread_mutexattr_t mattr;
|
|
pthread_mutexattr_init(&mattr);
|
|
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
|
|
pthread_mutex_init(&mutex, &mattr);
|
|
pthread_mutexattr_destroy(&mattr);
|
|
}
|
|
IpcMutex(const IpcMutex&) = delete;
|
|
|
|
~IpcMutex() {
|
|
pthread_mutex_destroy(&mutex);
|
|
}
|
|
|
|
void lock() {
|
|
pthread_mutex_lock(&mutex);
|
|
}
|
|
|
|
void unlock() {
|
|
pthread_mutex_unlock(&mutex);
|
|
}
|
|
|
|
native_handle_type native_handle() {
|
|
return &mutex;
|
|
}
|
|
|
|
IpcMutex& operator=(const IpcMutex&) = delete;
|
|
};
|
|
|
|
class IpcConditionVariable
|
|
{
|
|
pthread_cond_t cond;
|
|
|
|
public:
|
|
IpcConditionVariable()
|
|
{
|
|
pthread_condattr_t cvattr;
|
|
pthread_condattr_init(&cvattr);
|
|
pthread_condattr_setpshared(&cvattr, PTHREAD_PROCESS_SHARED);
|
|
pthread_cond_init(&cond, &cvattr);
|
|
pthread_condattr_destroy(&cvattr);
|
|
}
|
|
IpcConditionVariable(const IpcConditionVariable&) = delete;
|
|
|
|
~IpcConditionVariable() {
|
|
pthread_cond_destroy(&cond);
|
|
}
|
|
|
|
void notify_all() {
|
|
pthread_cond_broadcast(&cond);
|
|
}
|
|
|
|
void wait(std::unique_lock<IpcMutex>& lock) {
|
|
pthread_cond_wait(&cond, lock.mutex()->native_handle());
|
|
}
|
|
|
|
template<class Rep, class Period>
|
|
std::cv_status wait_for(std::unique_lock<IpcMutex>& lock,
|
|
const std::chrono::duration<Rep, Period>& rel_time)
|
|
{
|
|
timespec ts;
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
std::chrono::seconds seconds = std::chrono::duration_cast<std::chrono::seconds>(rel_time);
|
|
ts.tv_sec += seconds.count();
|
|
auto nanoTime = rel_time - seconds;
|
|
ts.tv_nsec += std::chrono::nanoseconds(nanoTime).count();
|
|
if (ts.tv_nsec >= 1000000000) {
|
|
ts.tv_sec++;
|
|
ts.tv_nsec -= 1000000000;
|
|
}
|
|
int rc = pthread_cond_timedwait(&cond, lock.mutex()->native_handle(), &ts);
|
|
return rc == ETIMEDOUT ? std::cv_status::timeout : std::cv_status::no_timeout;
|
|
}
|
|
|
|
IpcConditionVariable& operator=(const IpcConditionVariable&) = delete;
|
|
};
|
|
#endif // _!WIN32
|
|
|
|
class Multiboard
|
|
{
|
|
public:
|
|
static constexpr u32 G1_BASE = 0x05F7080;
|
|
static constexpr u32 G2_BASE = 0x1010000;
|
|
|
|
Multiboard();
|
|
~Multiboard();
|
|
|
|
u32 readG1(u32 addr, u32 size)
|
|
{
|
|
switch (addr)
|
|
{
|
|
case NAOMI_MBOARD_OFFSET_addr:
|
|
return offset;
|
|
|
|
case NAOMI_MBOARD_DATA_addr:
|
|
{
|
|
u32 bank = (sharedMem->status & 1) ? 0 : 0x80000;
|
|
u32 addr;
|
|
if (isMaster())
|
|
{
|
|
bank = 0x80000 - bank;
|
|
addr = offset + bank / 2;
|
|
}
|
|
else
|
|
{
|
|
addr = offset + (boardId - 1) * 0x10000 + bank / 2;
|
|
}
|
|
u16 data = sharedMem->data[addr & (MEM_SIZE - 1)];
|
|
DEBUG_LOG(NAOMI, "read NAOMI_COMM_DATA[%x]: %x (pc = %x)", addr, data, p_sh4rcb->cntx.pc);
|
|
offset++;
|
|
return data;
|
|
}
|
|
|
|
case 0x5f7074:
|
|
DEBUG_LOG(NAOMI, "5F7074 read: %d", reg74); // loops from 0 to ff
|
|
return reg74 & 0xff;
|
|
|
|
case 0x5f706C:
|
|
DEBUG_LOG(NAOMI, "5F706C read");
|
|
return 0; // written to C4 afterwards & 7 except if == 7
|
|
|
|
case G1_BASE + 0x08:
|
|
DEBUG_LOG(NAOMI, "5F7088 read");
|
|
return 0x80; // loops until bit 7 is set
|
|
|
|
case G1_BASE + 0x10:
|
|
DEBUG_LOG(NAOMI, "5F7090 read");
|
|
return 0x60; // ? or 0x61 or 0x62
|
|
|
|
case G1_BASE + 0x14:
|
|
DEBUG_LOG(NAOMI, "5F7094 read");
|
|
return 0x43; // set to 43 before
|
|
|
|
default:
|
|
DEBUG_LOG(NAOMI, "Unknown G1 register read<%d>: %x (pc = %x)", size, addr, p_sh4rcb->cntx.pc);
|
|
return 0xFFFF;
|
|
}
|
|
}
|
|
|
|
void writeG1(u32 addr, u32 size, u32 data)
|
|
{
|
|
switch (addr)
|
|
{
|
|
case NAOMI_MBOARD_OFFSET_addr:
|
|
DEBUG_LOG(NAOMI, "NAOMI_COMM_OFFSET = %x (pc = %x)", data, p_sh4rcb->cntx.pc);
|
|
offset = data;
|
|
break;
|
|
|
|
case NAOMI_MBOARD_DATA_addr:
|
|
{
|
|
DEBUG_LOG(NAOMI, "NAOMI_COMM_DATA[%x] = %x (pc = °%x)", offset, data, p_sh4rcb->cntx.pc);
|
|
u32 bank = (sharedMem->status & 1) ? 0 : 0x80000;
|
|
u32 addr;
|
|
if (isMaster())
|
|
{
|
|
bank = 0x80000 - bank;
|
|
addr = offset + bank / 2;
|
|
}
|
|
else
|
|
{
|
|
addr = offset + (boardId - 1) * 0x10000 + bank / 2;
|
|
}
|
|
sharedMem->data[addr & (MEM_SIZE - 1)] = data;
|
|
offset++;
|
|
}
|
|
break;
|
|
|
|
case 0x5f7070:
|
|
DEBUG_LOG(NAOMI, "5F7070 written: %d", data);
|
|
break;
|
|
|
|
case 0x5f7074:
|
|
DEBUG_LOG(NAOMI, "5F7074 written: %d", data);
|
|
reg74 = data;
|
|
startSlave();
|
|
break;
|
|
|
|
case 0x5f7058: // Set to 0 before DMA operation from multiboard
|
|
break;
|
|
|
|
case NAOMI_MBOARD_STATUS_addr:
|
|
// Set to 4 after writing most packets
|
|
if (isSlave())
|
|
{
|
|
if ((data & 4) != 0)
|
|
sharedMem->status.fetch_or(0x10 << boardId);
|
|
//else
|
|
// sharedMem->status.fetch_and(~(0x10 << boardId));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
DEBUG_LOG(NAOMI, "Unknown G1 register written<%d>: %x = %x (pc = %x)", size, addr, data, p_sh4rcb->cntx.pc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
u32 readG2Ext(u32 addr, u32 size)
|
|
{
|
|
//DEBUG_LOG(NAOMI, "g2ext_readMem<%d> %x (pc = %x)", size, addr, p_sh4rcb->cntx.pc);
|
|
switch (addr)
|
|
{
|
|
case G2_BASE + 0x08:
|
|
return 0x80; // loops until bit 7 is set
|
|
|
|
case G2_BASE + 0x10: // similar to 5F7090
|
|
return 0x60;
|
|
|
|
case G2_BASE + 0x14: // similar to 5F7094
|
|
return 0x43;
|
|
|
|
case G2_BASE + 0x94:
|
|
return 0; // ?
|
|
case G2_BASE + 0x98:
|
|
return 0; // ?
|
|
|
|
case G2_BASE + 0x9c: // similar to 5F7074
|
|
return isMaster() ? reg9c : 0;
|
|
|
|
case G2_BASE + 0xc0: // similar to 5F706C. need to match!
|
|
return 0;
|
|
|
|
case 0x1008000: // status reg
|
|
{
|
|
verify(size == 2);
|
|
// seems to determine if acting as master or slave
|
|
if (isSlave())
|
|
return 0;
|
|
u16 v = 0xff00 | sharedMem->status;
|
|
DEBUG_LOG(NAOMI, "g2ext_readMem status_reg %x", v);
|
|
return v;
|
|
}
|
|
|
|
default:
|
|
if ((addr - 0x1020000) < MEM_SIZE * 2)
|
|
{
|
|
u32 bank = (sharedMem->status & 1) ? 0x80000 : 0;
|
|
DEBUG_LOG(NAOMI, "g2ext_readMem<%d> %x -> %x", size, addr, sharedMem->data[(addr - 0x1020000 + bank) / 2]);
|
|
verify(size >= 2);
|
|
u32 offset;
|
|
if (isSlave())
|
|
{
|
|
return 0;
|
|
bank = 0x80000 - bank;
|
|
if (addr >= 0x1040000) {
|
|
INFO_LOG(NAOMI, "Read shared mem out of bound for slave: %x", addr);
|
|
break;
|
|
}
|
|
offset = (addr - 0x1020000 + bank + (boardId - 1) * 0x20000) / 2;
|
|
}
|
|
else
|
|
offset = (addr - 0x1020000 + bank) / 2;
|
|
|
|
if (size == 2)
|
|
return sharedMem->data[offset];
|
|
else
|
|
return *(u32 *)&sharedMem->data[offset];
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void writeG2Ext(u32 addr, u32 size, u32 data)
|
|
{
|
|
//DEBUG_LOG(NAOMI, "g2ext_writeMem<%d> %x = %x", size, addr, data);
|
|
switch (addr) {
|
|
case 0x1008000: // status reg
|
|
verify(size == 2);
|
|
if (isMaster())
|
|
sharedMem->status = data;
|
|
DEBUG_LOG(NAOMI, "g2ext_writeMem status_reg %x", data);
|
|
break;
|
|
|
|
case G2_BASE + 0x9c:
|
|
reg9c = data;
|
|
break;
|
|
|
|
case G2_BASE + 0xa0: // LEDs
|
|
DEBUG_LOG(NAOMI, "G2 leds %x", data);
|
|
break;
|
|
|
|
default:
|
|
if ((addr - 0x1020000) < MEM_SIZE * 2)
|
|
{
|
|
DEBUG_LOG(NAOMI, "g2ext_writeMem<%d> %x: %x", size, addr, data);
|
|
verify(size >= 2);
|
|
u32 bank = (sharedMem->status & 1) ? 0x80000 : 0;
|
|
if (isSlave())
|
|
{
|
|
break;
|
|
bank = 0x80000 - bank;
|
|
if (addr >= 0x1040000) {
|
|
INFO_LOG(NAOMI, "Write shared mem out of bound for slave: %x", addr);
|
|
break;
|
|
}
|
|
u32 offset = (addr - 0x1020000 + bank + (boardId - 1) * 0x20000) / 2;
|
|
if (size == 2)
|
|
sharedMem->data[offset] = data;
|
|
else
|
|
*(u32 *)&sharedMem->data[offset] = data;
|
|
}
|
|
else
|
|
{
|
|
u32 offset = (addr - 0x1020000 + bank) / 2;
|
|
if ((sharedMem->status & 2) != 0 || addr >= 0x1040000)
|
|
{
|
|
if (size == 2)
|
|
sharedMem->data[offset] = data;
|
|
else
|
|
*(u32 *)&sharedMem->data[offset] = data;
|
|
}
|
|
if (addr < 0x1040000) // FIXME this is weird
|
|
{
|
|
if ((sharedMem->status & 4) != 0)
|
|
{
|
|
if (size == 2)
|
|
sharedMem->data[offset + 0x10000] = data;
|
|
else
|
|
*(u32 *)&sharedMem->data[offset + 0x10000] = data;
|
|
}
|
|
if (sharedMem->status & 8)
|
|
{
|
|
if (size == 2)
|
|
sharedMem->data[offset + 0x20000] = data;
|
|
else
|
|
*(u32 *)&sharedMem->data[offset + 0x20000] = data;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool dmaStart()
|
|
{
|
|
if (isMaster())
|
|
return false;
|
|
|
|
DEBUG_LOG(NAOMI, "Multiboard DMA start addr %08X len %d", SB_GDSTAR, SB_GDLEN);
|
|
verify(1 == SB_GDDIR);
|
|
u32 start = SB_GDSTAR & 0x1FFFFFE0;
|
|
u32 len = (SB_GDLEN + 31) & ~31;
|
|
u32 bank = (sharedMem->status & 1) ? 0 : 0x80000;
|
|
u32 *src = (u32 *)&sharedMem->data[(boardId - 1) * 0x10000 + bank / 2];
|
|
|
|
WriteMemBlock_nommu_ptr(start, src, len);
|
|
SB_GDSTARD = start + len;
|
|
SB_GDLEND = len;
|
|
|
|
return true;
|
|
}
|
|
|
|
void reset()
|
|
{
|
|
if (isSlave())
|
|
sharedMem->status.fetch_and(~(0x10 << boardId));
|
|
}
|
|
|
|
void syncWait();
|
|
|
|
private:
|
|
bool isMaster() const { return boardId == 0; }
|
|
bool isSlave() const { return boardId != 0; }
|
|
|
|
void startSlave();
|
|
|
|
static constexpr size_t MEM_SIZE = 0x100000 / sizeof(u16);
|
|
struct SharedMemory
|
|
{
|
|
std::atomic<u16> status;
|
|
IpcMutex mutex;
|
|
IpcConditionVariable cond;
|
|
std::atomic<bool> boardReady[4];
|
|
std::atomic<bool> boardSynced[4];
|
|
std::atomic<bool> exit;
|
|
u16 data[MEM_SIZE];
|
|
};
|
|
int boardId = 0;
|
|
u32 offset = 0;
|
|
u16 reg74 = 0;
|
|
u32 reg9c = 0;
|
|
SharedMemory *sharedMem;
|
|
#ifdef _WIN32
|
|
HANDLE mapFile;
|
|
#else
|
|
std::string sharedMemFileName;
|
|
#endif
|
|
int boardCount = 0;
|
|
bool slaveStarted = false;
|
|
int schedId;
|
|
};
|
|
|
|
#else // !NAOMI_MULTIBOARD
|
|
|
|
class Multiboard
|
|
{
|
|
public:
|
|
u32 readG1(u32 addr, u32 size) {
|
|
return 0;
|
|
}
|
|
void writeG1(u32 addr, u32 size, u32 data) { }
|
|
|
|
u32 readG2Ext(u32 addr, u32 size) {
|
|
return 0;
|
|
}
|
|
void writeG2Ext(u32 addr, u32 size, u32 data) { }
|
|
|
|
bool dmaStart() {
|
|
return false;
|
|
}
|
|
void reset() { }
|
|
|
|
void syncWait() { }
|
|
};
|
|
|
|
#endif // !NAOMI_MULTIBOARD
|