Files
Joe Mattiello e9f4020c56 maplelink: keep the link registry alive through static destruction (#2577)
MapleDevices[][] in maple_if.cpp holds shared_ptr<maple_device>s with
static storage, so the VMUs are destroyed when the process exits.
~maple_sega_vmu() clears the LCD on any attached maple link, which goes
through MapleLink::GetMapleLink() and locks MapleLink::Mutex, another
object with static storage, in another translation unit, that may
already be gone. With libc++ locking a destroyed mutex throws
("mutex lock failed: Invalid argument") and the process aborts; the
unit test binary exits with SIGABRT on macOS after every test passed.
On glibc the same lock happens to succeed, which is why Linux never
showed it. The Links table is read in the same state.

Give both to function-local statics that are allocated once and never
destroyed, so they outlive everything that can still reach them.

Co-authored-by: Claude Fable 5.1 <[email protected]>
[ci skip]
2026-10-21 12:08:48 +02:00

100 lines
3.4 KiB
C++

/*
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"
#include "hw/maple/maple_devs.h"
#include "emulator.h"
#include <array>
#include <list>
#include <memory>
#include <mutex>
// parent interface of DreamLink and DreamPotato for use by hw/maple
class MapleLink : public std::enable_shared_from_this<MapleLink>
{
public:
using Ptr = std::shared_ptr<MapleLink>;
virtual ~MapleLink() = default;
//! Sends a message to the controller, not expecting a response
virtual bool send(const MapleMsg& msg) = 0;
//! Sends a message to the controller and waits for a response
virtual bool sendReceive(const MapleMsg& txMsg, MapleMsg& rxMsg) = 0;
//! Called on GetLastError command over previous writes
//! @param[in] msg The message which contains GetLastError command
//! @return true iff successful
virtual bool handleGetLastError(const MapleMsg& msg) = 0;
//! True if VMU reads and writes should be sent to the device
virtual bool storageEnabled() = 0;
//! True if the link is operational
virtual bool isConnected() = 0;
//! @return number of active links made for this MapleLink on the given bus
std::size_t activeLinkCount(int bus) const;
//! Returns the maple link at the given location if any
static Ptr GetMapleLink(int bus, int port) {
std::lock_guard<std::mutex> _(mutex());
std::list<MapleLink::Ptr>& list = links()[bus][port];
if (list.empty())
return nullptr;
return list.front();
}
//! True if any maple link currently has storage enabled
static bool StorageEnabled();
protected:
//! True if a game has been started
bool isGameStarted() const;
void registerLink(int bus, int port);
void unregisterLink(int bus, int port);
int bus = -1;
u32 ports = 0;
private:
using LinkTable = std::array<std::array<std::list<Ptr>, 2>, 4>;
// Never destroyed: ~maple_sega_vmu() reaches GetMapleLink() from the
// static destructor of MapleDevices[][] at process exit, after objects
// with static storage in other translation units may already be gone.
// Locking a destroyed std::mutex throws (libc++: "mutex lock failed").
static std::mutex& mutex();
//! Indexed by [bus][extension port][priority idx]
//! Multiple links may be registered for a bus/port, and the last added to front takes precedence
static LinkTable& links();
};
class BaseMapleLink : public MapleLink
{
public:
~BaseMapleLink();
bool storageEnabled() override;
bool handleGetLastError(const MapleMsg& msg) override;
protected:
BaseMapleLink(bool storageSupported);
void disableStorage(); // Disable VMU storage for this link
virtual void gameStarted();
//! When called, do teardown stuff (vmu screen reset is handled by maple_sega_vmu)
virtual void gameTermination() {}
const bool storageSupported;
bool vmuStorage = false;
private:
static void eventHandler(Event event, void *p);
};