libNX HID initialization

What was added:
* HID Service
* Support for Mutexes and Conditional Variables
What was improved:
* Service API now creates one instance per Session rather than a single instance for all Sessions
* Changed std::map objects into std::unordered_map in KProcess
* Comments on enumeration values
This commit is contained in:
◱ PixelyIon 2019-10-16 18:11:30 +05:30
parent ec71735ece
commit 2476c5d48a
14 changed files with 388 additions and 70 deletions

View File

@ -55,6 +55,7 @@ namespace skyline {
constexpr u8 DefaultPriority = 31; //!< The default priority of a process constexpr u8 DefaultPriority = 31; //!< The default priority of a process
constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1 constexpr std::pair<int8_t, int8_t> PriorityAn = {19, -8}; //!< The range of priority for Android, taken from https://medium.com/mindorks/exploring-android-thread-priority-5d0542eebbd1
constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch constexpr std::pair<u8, u8> PriorityNin = {0, 63}; //!< The range of priority for the Nintendo Switch
constexpr u32 mtxOwnerMask = 0xBFFFFFFF; //!< The mask of values which contain the owner of a mutex
// IPC // IPC
constexpr size_t TlsIpcSize = 0x100; //!< The size of the IPC command buffer in a TLS slot constexpr size_t TlsIpcSize = 0x100; //!< The size of the IPC command buffer in a TLS slot
constexpr u8 PortSize = 0x8; //!< The size of a port name string constexpr u8 PortSize = 0x8; //!< The size of a port name string
@ -235,6 +236,14 @@ namespace skyline {
void List(std::shared_ptr<Logger> logger); void List(std::shared_ptr<Logger> logger);
}; };
/**
* @brief Returns the current time in nanoseconds
* @return The current time in nanoseconds
*/
inline long long int GetCurrTimeNs() {
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count();
}
// Predeclare some classes here as we use them in DeviceState // Predeclare some classes here as we use them in DeviceState
class NCE; class NCE;
namespace kernel { namespace kernel {

View File

@ -75,11 +75,13 @@ namespace skyline::kernel::service::am {
std::shared_ptr<type::KEvent> messageEvent{}; std::shared_ptr<type::KEvent> messageEvent{};
enum class ApplicationStatus : u8 { enum class ApplicationStatus : u8 {
InFocus = 1, OutOfFocus = 2 InFocus = 1, //!< The application is in foreground
OutOfFocus = 2 //!< The application is in the background
}; };
enum class OperationMode : u8 { enum class OperationMode : u8 {
Handheld = 0, Docked = 1 Handheld = 0, //!< The device is in handheld mode
Docked = 1 //!< The device is in docked mode
} operationMode; } operationMode;
public: public:
ICommonStateGetter(const DeviceState &state, ServiceManager &manager); ICommonStateGetter(const DeviceState &state, ServiceManager &manager);

View File

@ -69,7 +69,6 @@ namespace skyline::kernel::service {
public: public:
Service serviceType; //!< Which service this is Service serviceType; //!< Which service this is
uint numSessions{}; //<! The amount of active sessions
const bool hasLoop; //<! If the service has a loop or not const bool hasLoop; //<! If the service has a loop or not
/** /**

View File

@ -2,14 +2,6 @@
#include <os.h> #include <os.h>
namespace skyline::kernel::service::hid { namespace skyline::kernel::service::hid {
hid::hid(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::hid, {
{0x0, SFunc(hid::CreateAppletResource)}
}) {}
void hid::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
resource = std::static_pointer_cast<IAppletResource>(manager.NewService(Service::hid_IAppletResource, session, response));
}
IAppletResource::IAppletResource(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::hid_IAppletResource, { IAppletResource::IAppletResource(const DeviceState &state, ServiceManager& manager) : BaseService(state, manager, false, Service::hid_IAppletResource, {
{0x0, SFunc(IAppletResource::GetSharedMemoryHandle)} {0x0, SFunc(IAppletResource::GetSharedMemoryHandle)}
}) {} }) {}
@ -18,4 +10,76 @@ namespace skyline::kernel::service::hid {
hidSharedMemory = state.os->MapSharedKernel(0, constant::hidSharedMemSize, memory::Permission(true, false, false), memory::Permission(true, true, false), memory::Type::SharedMemory); hidSharedMemory = state.os->MapSharedKernel(0, constant::hidSharedMemSize, memory::Permission(true, false, false), memory::Permission(true, true, false), memory::Type::SharedMemory);
response.copyHandles.push_back(state.thisProcess->InsertItem<type::KSharedMemory>(hidSharedMemory)); response.copyHandles.push_back(state.thisProcess->InsertItem<type::KSharedMemory>(hidSharedMemory));
} }
hid::hid(const DeviceState &state, ServiceManager &manager) : BaseService(state, manager, false, Service::hid, {
{0x0, SFunc(hid::CreateAppletResource)},
{0x64, SFunc(hid::SetSupportedNpadStyleSet)},
{0x66, SFunc(hid::SetSupportedNpadIdType)},
{0x67, SFunc(hid::ActivateNpad)},
{0x78, SFunc(hid::SetNpadJoyHoldType)},
{0x7A, SFunc(hid::SetNpadJoyAssignmentModeSingleByDefault)},
{0x7B, SFunc(hid::SetNpadJoyAssignmentModeSingle)},
{0x7C, SFunc(hid::SetNpadJoyAssignmentModeDual)}
}) {}
void hid::CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
resource = std::static_pointer_cast<IAppletResource>(manager.NewService(Service::hid_IAppletResource, session, response));
}
void hid::SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u32 styleSet;
u64 appletUserId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
styleSet = *reinterpret_cast<StyleSet *>(&input->styleSet);
state.logger->Write(Logger::Info, "Controller Support: Pro-Controller: {} Joy-Con: Handheld: {}, Dual: {}, L: {}, R: {} GameCube: {} PokeBall: {} NES: {} NES Handheld: {} SNES: {}", static_cast<bool>(styleSet->pro_controller), static_cast<bool>(styleSet->joycon_handheld), static_cast<bool>(styleSet->joycon_dual), static_cast<bool>(styleSet->joycon_left), static_cast<bool>
(styleSet->joycon_right), static_cast<bool>(styleSet->gamecube), static_cast<bool>(styleSet->pokeball), static_cast<bool>(styleSet->nes), static_cast<bool>(styleSet->nes_handheld), static_cast<bool>(styleSet->snes));
}
void hid::SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
const auto &buffer = request.vecBufX[0];
uint numId = buffer->size / sizeof(NpadId);
u64 address = buffer->Address();
for (uint i = 0; i < numId; i++) {
auto id = state.thisProcess->ReadMemory<NpadId>(address);
deviceMap[id] = JoyConDevice(id);
address += sizeof(NpadId);
}
}
void hid::ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {}
void hid::SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
NpadId controllerId;
u64 appletUserId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
deviceMap[input->controllerId].assignment = JoyConAssignment::Single;
}
void hid::SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
u64 appletUserId;
JoyConOrientation orientation;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
orientation = input->orientation;
}
void hid::SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
NpadId controllerId;
u64 appletUserId;
JoyConSide joyDeviceType;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
deviceMap[input->controllerId].assignment = JoyConAssignment::Single;
deviceMap[input->controllerId].side = input->joyDeviceType;
}
void hid::SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response) {
struct InputStruct {
NpadId controllerType;
u64 appletUserId;
} *input = reinterpret_cast<InputStruct *>(request.cmdArg);
deviceMap[input->controllerType].assignment = JoyConAssignment::Dual;
}
} }

View File

@ -29,7 +29,87 @@ namespace skyline::kernel::service::hid {
*/ */
class hid : public BaseService { class hid : public BaseService {
private: private:
std::shared_ptr<IAppletResource> resource{}; /**
* @brief This holds the controller styles supported by an application
*/
struct StyleSet {
bool pro_controller : 1; //!< The Pro Controller
bool joycon_handheld : 1; //!< Joy-Cons in handheld mode
bool joycon_dual : 1; //!< Joy-Cons in a pair
bool joycon_left : 1; //!< Left Joy-Con only
bool joycon_right : 1; //!< Right Joy-Con only
bool gamecube : 1; //!< GameCube controller
bool pokeball : 1; //!< Poké Ball Plus controller
bool nes : 1; //!< NES controller
bool nes_handheld : 1; //!< NES controller in handheld mode
bool snes : 1; //!< SNES controller
u32 : 22;
};
static_assert(sizeof(StyleSet) == 4);
/**
* @brief This holds a Controller's ID (https://switchbrew.org/wiki/HID_services#NpadIdType)
*/
enum class NpadId : u32 {
Player1 = 0x0, //!< 1st Player
Player2 = 0x1, //!< 2nd Player
Player3 = 0x2, //!< 3rd Player
Player4 = 0x3, //!< 4th Player
Player5 = 0x4, //!< 5th Player
Player6 = 0x5, //!< 6th Player
Player7 = 0x6, //!< 7th Player
Player8 = 0x7, //!< 8th Player
Unknown = 0x10, //!< Unknown
Handheld = 0x20 //!< Handheld mode
};
/**
* @brief This holds a Controller's Assignment mode
*/
enum class JoyConAssignment {
Dual, //!< Dual Joy-Cons
Single, //!< Single Joy-Con
Unset //!< Not set
};
/**
* @brief This holds which Joy-Con to use Single mode (Not if SetNpadJoyAssignmentModeSingleByDefault is used)
*/
enum class JoyConSide : i64 {
Left, //!< Left Joy-Con
Right, //!< Right Joy-Con
Unset //!< Not set
};
/**
* @brief This denotes the orientation of the Joy-Con(s)
*/
enum class JoyConOrientation : u64 {
Vertical, //!< The Joy-Con is held vertically
Horizontal, //!< The Joy-Con is held horizontally
Unset //!< Not set
};
// TODO: Replace JoyConDevice with base NpadDevice class
/**
* @brief This holds the state of a single Npad device
*/
struct JoyConDevice {
NpadId id; //!< The ID of this device
JoyConAssignment assignment{JoyConAssignment::Unset}; //!< The assignment mode of this device
JoyConSide side{JoyConSide::Unset}; //!< The type of the device
JoyConDevice() : id(NpadId::Unknown) {}
JoyConDevice(const NpadId &id) : id(id) {}
};
std::shared_ptr<IAppletResource> resource{}; //!< A shared pointer to the applet resource
std::optional<StyleSet> styleSet; //!< The controller styles supported by the application
std::unordered_map<NpadId, JoyConDevice> deviceMap; //!< Mapping from a controller's ID to it's corresponding JoyConDevice
JoyConOrientation orientation{JoyConOrientation::Unset}; //!< The Orientation of the Joy-Con(s)
public: public:
hid(const DeviceState &state, ServiceManager& manager); hid(const DeviceState &state, ServiceManager& manager);
@ -37,5 +117,40 @@ namespace skyline::kernel::service::hid {
* @brief This returns an IAppletResource (https://switchbrew.org/wiki/HID_services#CreateAppletResource) * @brief This returns an IAppletResource (https://switchbrew.org/wiki/HID_services#CreateAppletResource)
*/ */
void CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response); void CreateAppletResource(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This sets the style of controllers supported (https://switchbrew.org/wiki/HID_services#SetSupportedNpadStyleSet)
*/
void SetSupportedNpadStyleSet(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This sets the NpadIds which are supported (https://switchbrew.org/wiki/HID_services#SetSupportedNpadIdType)
*/
void SetSupportedNpadIdType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief This requests the activation of a controller. This is stubbed as we don't have to activate anything.
*/
void ActivateNpad(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Sets the Joy-Con hold mode (https://switchbrew.org/wiki/HID_services#SetNpadJoyHoldType)
*/
void SetNpadJoyHoldType(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Sets the Joy-Con assignment mode to Single by default (https://switchbrew.org/wiki/HID_services#SetNpadJoyAssignmentModeSingleByDefault)
*/
void SetNpadJoyAssignmentModeSingleByDefault(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Sets the Joy-Con assignment mode to Single (https://switchbrew.org/wiki/HID_services#SetNpadJoyAssignmentModeSingle)
*/
void SetNpadJoyAssignmentModeSingle(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
/**
* @brief Sets the Joy-Con assignment mode to Dual (https://switchbrew.org/wiki/HID_services#SetNpadJoyAssignmentModeDual)
*/
void SetNpadJoyAssignmentModeDual(type::KSession &session, ipc::IpcRequest &request, ipc::IpcResponse &response);
}; };
} }

View File

@ -12,64 +12,60 @@ namespace skyline::kernel::service {
std::shared_ptr<BaseService> ServiceManager::GetService(const Service serviceType) { std::shared_ptr<BaseService> ServiceManager::GetService(const Service serviceType) {
std::shared_ptr<BaseService> serviceObj; std::shared_ptr<BaseService> serviceObj;
if (serviceMap.find(serviceType) == serviceMap.end()) { switch (serviceType) {
switch (serviceType) {
case Service::sm: case Service::sm:
serviceMap[serviceType] = std::make_shared<sm::sm>(state, *this); serviceObj = std::make_shared<sm::sm>(state, *this);
break; break;
case Service::fatal_u: case Service::fatal_u:
serviceMap[serviceType] = std::make_shared<fatal::fatalU>(state, *this); serviceObj = std::make_shared<fatal::fatalU>(state, *this);
break; break;
case Service::set_sys: case Service::set_sys:
serviceMap[serviceType] = std::make_shared<set::sys>(state, *this); serviceObj = std::make_shared<set::sys>(state, *this);
break; break;
case Service::apm: case Service::apm:
serviceMap[serviceType] = std::make_shared<apm::apm>(state, *this); serviceObj = std::make_shared<apm::apm>(state, *this);
break; break;
case Service::apm_ISession: case Service::apm_ISession:
serviceMap[serviceType] = std::make_shared<apm::ISession>(state, *this); serviceObj = std::make_shared<apm::ISession>(state, *this);
break; break;
case Service::am_appletOE: case Service::am_appletOE:
serviceMap[serviceType] = std::make_shared<am::appletOE>(state, *this); serviceObj = std::make_shared<am::appletOE>(state, *this);
break; break;
case Service::am_IApplicationProxy: case Service::am_IApplicationProxy:
serviceMap[serviceType] = std::make_shared<am::IApplicationProxy>(state, *this); serviceObj = std::make_shared<am::IApplicationProxy>(state, *this);
break; break;
case Service::am_ICommonStateGetter: case Service::am_ICommonStateGetter:
serviceMap[serviceType] = std::make_shared<am::ICommonStateGetter>(state, *this); serviceObj = std::make_shared<am::ICommonStateGetter>(state, *this);
break; break;
case Service::am_IWindowController: case Service::am_IWindowController:
serviceMap[serviceType] = std::make_shared<am::IWindowController>(state, *this); serviceObj = std::make_shared<am::IWindowController>(state, *this);
break; break;
case Service::am_IAudioController: case Service::am_IAudioController:
serviceMap[serviceType] = std::make_shared<am::IAudioController>(state, *this); serviceObj = std::make_shared<am::IAudioController>(state, *this);
break; break;
case Service::am_IDisplayController: case Service::am_IDisplayController:
serviceMap[serviceType] = std::make_shared<am::IDisplayController>(state, *this); serviceObj = std::make_shared<am::IDisplayController>(state, *this);
break; break;
case Service::am_ISelfController: case Service::am_ISelfController:
serviceMap[serviceType] = std::make_shared<am::ISelfController>(state, *this); serviceObj = std::make_shared<am::ISelfController>(state, *this);
break; break;
case Service::am_ILibraryAppletCreator: case Service::am_ILibraryAppletCreator:
serviceMap[serviceType] = std::make_shared<am::ILibraryAppletCreator>(state, *this); serviceObj = std::make_shared<am::ILibraryAppletCreator>(state, *this);
break; break;
case Service::am_IApplicationFunctions: case Service::am_IApplicationFunctions:
serviceMap[serviceType] = std::make_shared<am::IApplicationFunctions>(state, *this); serviceObj = std::make_shared<am::IApplicationFunctions>(state, *this);
break; break;
case Service::am_IDebugFunctions: case Service::am_IDebugFunctions:
serviceMap[serviceType] = std::make_shared<am::IDebugFunctions>(state, *this); serviceObj = std::make_shared<am::IDebugFunctions>(state, *this);
break; break;
case Service::hid: case Service::hid:
serviceMap[serviceType] = std::make_shared<hid::hid>(state, *this); serviceObj = std::make_shared<hid::hid>(state, *this);
break; break;
case Service::hid_IAppletResource: case Service::hid_IAppletResource:
serviceMap[serviceType] = std::make_shared<hid::IAppletResource>(state, *this); serviceObj = std::make_shared<hid::IAppletResource>(state, *this);
break; break;
} }
serviceObj = serviceMap[serviceType]; serviceVec.push_back(serviceObj);
} else
serviceObj = serviceMap.at(serviceType);
serviceObj->numSessions++;
return serviceObj; return serviceObj;
} }
@ -89,20 +85,19 @@ namespace skyline::kernel::service {
} }
void ServiceManager::CloseSession(const handle_t handle) { void ServiceManager::CloseSession(const handle_t handle) {
auto object = state.thisProcess->GetHandle<type::KSession>(handle); auto session = state.thisProcess->GetHandle<type::KSession>(handle);
if (object->serviceStatus == type::KSession::ServiceStatus::Open) { if (session->serviceStatus == type::KSession::ServiceStatus::Open) {
if (object->isDomain) { if (session->isDomain) {
for (const auto &service : object->domainTable) for (const auto &[objectId, service] : session->domainTable)
if (!(service.second->numSessions--)) serviceVec.erase(std::remove(serviceVec.begin(), serviceVec.end(), service), serviceVec.end());
serviceMap.erase(service.second->serviceType); } else
} else if (!(serviceMap.at(object->serviceType)->numSessions--)) serviceVec.erase(std::remove(serviceVec.begin(), serviceVec.end(), session->serviceObject), serviceVec.end());
serviceMap.erase(object->serviceType); session->serviceStatus = type::KSession::ServiceStatus::Closed;
object->serviceStatus = type::KSession::ServiceStatus::Closed;
} }
}; };
void ServiceManager::Loop() { void ServiceManager::Loop() {
for (auto&[index, service] : serviceMap) for (auto &service : serviceVec)
if (service->hasLoop) if (service->hasLoop)
service->Loop(); service->Loop();
} }
@ -127,8 +122,7 @@ namespace skyline::kernel::service {
service->HandleRequest(*session, request, response); service->HandleRequest(*session, request, response);
break; break;
case ipc::DomainCommand::CloseVHandle: case ipc::DomainCommand::CloseVHandle:
if (!(service->numSessions--)) serviceVec.erase(std::remove(serviceVec.begin(), serviceVec.end(), service), serviceVec.end());
serviceMap.erase(service->serviceType);
session->domainTable.erase(request.domain->object_id); session->domainTable.erase(request.domain->object_id);
break; break;
} }

View File

@ -11,8 +11,12 @@ namespace skyline::kernel::service {
class ServiceManager { class ServiceManager {
private: private:
const DeviceState &state; //!< The state of the device const DeviceState &state; //!< The state of the device
std::unordered_map<Service, std::shared_ptr<BaseService>> serviceMap; //!< A map from it's type to a BaseService object std::vector<std::shared_ptr<BaseService>> serviceVec; //!< A vector with all of the services
/**
* @param serviceType The type of service requested
* @return A shared pointer to an instance of the service
*/
std::shared_ptr<BaseService> GetService(const Service serviceType); std::shared_ptr<BaseService> GetService(const Service serviceType);
public: public:

View File

@ -59,7 +59,7 @@ namespace skyline::kernel::svc {
case 2: case 2:
state.thisThread->status = type::KThread::ThreadStatus::Runnable; // Will cause the application to awaken on the next iteration of the main loop state.thisThread->status = type::KThread::ThreadStatus::Runnable; // Will cause the application to awaken on the next iteration of the main loop
default: default:
state.thisThread->timeoutEnd = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() + in; state.thisThread->timeout = GetCurrTimeNs() + in;
state.thisThread->status = type::KThread::ThreadStatus::Sleeping; state.thisThread->status = type::KThread::ThreadStatus::Sleeping;
} }
} }
@ -98,7 +98,7 @@ namespace skyline::kernel::svc {
} }
void WaitSynchronization(DeviceState &state) { void WaitSynchronization(DeviceState &state) {
state.thisThread->timeoutEnd = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count() + state.nce->GetRegister(Xreg::X3); state.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3);
auto numHandles = state.nce->GetRegister(Wreg::W2); auto numHandles = state.nce->GetRegister(Wreg::W2);
if (numHandles > constant::MaxSyncHandles) { if (numHandles > constant::MaxSyncHandles) {
state.nce->SetRegister(Wreg::W0, constant::status::MaxHandles); state.nce->SetRegister(Wreg::W0, constant::status::MaxHandles);
@ -126,7 +126,54 @@ namespace skyline::kernel::svc {
state.thisThread->waitObjects.push_back(syncObject); state.thisThread->waitObjects.push_back(syncObject);
syncObject->waitThreads.push_back(state.thisThread->pid); syncObject->waitThreads.push_back(state.thisThread->pid);
} }
state.thisThread->status = type::KThread::ThreadStatus::Waiting; state.thisThread->status = type::KThread::ThreadStatus::WaitSync;
}
void ArbitrateLock(DeviceState &state) {
if (state.nce->GetRegister(Wreg::W2) != state.thisThread->handle)
throw exception("A process requested locking a thread on behalf of another process");
state.thisProcess->MutexLock(state.nce->GetRegister(Xreg::X1));
state.nce->SetRegister(Wreg::W0, constant::status::Success);
}
void ArbitrateUnlock(DeviceState &state) {
state.thisProcess->MutexUnlock(state.nce->GetRegister(Xreg::X0));
state.nce->SetRegister(Wreg::W0, constant::status::Success);
}
void WaitProcessWideKeyAtomic(DeviceState &state) {
auto mtxAddr = state.nce->GetRegister(Xreg::X0);
if (state.nce->GetRegister(Wreg::W2) != state.thisThread->handle)
throw exception("svcWaitProcessWideKeyAtomic was called on behalf of another thread");
state.thisProcess->MutexUnlock(mtxAddr);
auto &cvarVec = state.thisProcess->condVarMap[state.nce->GetRegister(Xreg::X1)];
for (auto thread = cvarVec.begin();; thread++) {
if ((*thread)->priority < state.thisThread->priority) {
cvarVec.insert(thread, state.thisThread);
break;
} else if (thread + 1 == cvarVec.end()) {
cvarVec.push_back(state.thisThread);
break;
}
}
state.thisThread->status = type::KThread::ThreadStatus::WaitCondVar;
state.thisThread->timeout = GetCurrTimeNs() + state.nce->GetRegister(Xreg::X3);
state.nce->SetRegister(Wreg::W0, constant::status::Success);
}
void SignalProcessWideKey(DeviceState &state) {
auto address = state.nce->GetRegister(Xreg::X0);
auto count = state.nce->GetRegister(Wreg::W1);
state.nce->SetRegister(Wreg::W0, constant::status::Success);
if (!state.thisProcess->condVarMap.count(address))
return; // No threads to awaken
auto &cvarVec = state.thisProcess->condVarMap[address];
count = std::min(count, static_cast<u32>(cvarVec.size()));
for (uint index = 0; index < count; index++)
cvarVec[index]->status = type::KThread::ThreadStatus::Runnable;
cvarVec.erase(cvarVec.begin(), cvarVec.begin() + count);
if (cvarVec.empty())
state.thisProcess->condVarMap.erase(address);
} }
void ConnectToNamedPort(DeviceState &state) { void ConnectToNamedPort(DeviceState &state) {
@ -150,7 +197,7 @@ namespace skyline::kernel::svc {
std::string::size_type pos = 0; std::string::size_type pos = 0;
while ((pos = debug.find("\r\n", pos)) != std::string::npos) while ((pos = debug.find("\r\n", pos)) != std::string::npos)
debug.erase(pos, 2); debug.erase(pos, 2);
state.logger->Write(Logger::Info, "svcOutputDebugString: {}", debug); state.logger->Write(Logger::Info, "Debug Output: {}", debug);
state.nce->SetRegister(Wreg::W0, 0); state.nce->SetRegister(Wreg::W0, 0);
} }

View File

@ -96,6 +96,26 @@ namespace skyline {
*/ */
void WaitSynchronization(DeviceState &state); void WaitSynchronization(DeviceState &state);
/**
* @brief Locks a specified mutex
*/
void ArbitrateLock(DeviceState &state);
/**
* @brief Unlocks a specified mutex
*/
void ArbitrateUnlock(DeviceState &state);
/**
* @brief Waits on a process-wide key (Conditional-Variable)
*/
void WaitProcessWideKeyAtomic(DeviceState &state);
/**
* @brief Signals a process-wide key (Conditional-Variable)
*/
void SignalProcessWideKey(DeviceState &state);
/** /**
* @brief Connects to a named IPC port * @brief Connects to a named IPC port
*/ */
@ -146,10 +166,10 @@ namespace skyline {
nullptr, // 0x17 nullptr, // 0x17
WaitSynchronization, // 0x18 WaitSynchronization, // 0x18
nullptr, // 0x19 nullptr, // 0x19
nullptr, // 0x1a ArbitrateLock, // 0x1a
nullptr, // 0x1b ArbitrateUnlock, // 0x1b
nullptr, // 0x1c WaitProcessWideKeyAtomic, // 0x1c
nullptr, // 0x1d SignalProcessWideKey, // 0x1d
nullptr, // 0x1e nullptr, // 0x1e
ConnectToNamedPort, // 0x1f ConnectToNamedPort, // 0x1f
nullptr, // 0x20 nullptr, // 0x20

View File

@ -101,4 +101,42 @@ namespace skyline::kernel::type {
sharedSize += region.second->size; sharedSize += region.second->size;
return sharedSize; return sharedSize;
} }
void KProcess::MutexLock(u64 address) {
auto mtxVec = state.thisProcess->mutexMap[address];
u32 mtxVal = state.thisProcess->ReadMemory<u32>(address);
if (mtxVec.empty()) {
mtxVal = (mtxVal & ~constant::mtxOwnerMask) | state.thisThread->handle;
state.thisProcess->WriteMemory(mtxVal, address);
} else {
for (auto thread = mtxVec.begin();; thread++) {
if ((*thread)->priority < state.thisThread->priority) {
mtxVec.insert(thread, state.thisThread);
break;
} else if (thread + 1 == mtxVec.end()) {
mtxVec.push_back(state.thisThread);
break;
}
}
state.thisThread->status = KThread::ThreadStatus::WaitMutex;
}
}
void KProcess::MutexUnlock(u64 address) {
auto mtxVec = state.thisProcess->mutexMap[address];
u32 mtxVal = state.thisProcess->ReadMemory<u32>(address);
if ((mtxVal & constant::mtxOwnerMask) != state.thisThread->pid)
throw exception("A non-owner thread tried to release a mutex");
if (mtxVec.empty()) {
mtxVal = 0;
} else {
auto &thread = mtxVec.front();
mtxVal = (mtxVal & ~constant::mtxOwnerMask) | thread->handle;
thread->status = KThread::ThreadStatus::Runnable;
mtxVec.erase(mtxVec.begin());
if (!mtxVec.empty())
mtxVal |= (~constant::mtxOwnerMask);
}
state.thisProcess->WriteMemory(mtxVal, address);
}
} }

View File

@ -57,14 +57,20 @@ namespace skyline::kernel::type {
int memFd; //!< The file descriptor to the memory of the process int memFd; //!< The file descriptor to the memory of the process
public: public:
enum class ProcessStatus { Created, CreatedAttached, Started, Crashed, StartedAttached, Exiting, Exited, DebugSuspended } status = ProcessStatus::Created; //!< The state of the process enum class ProcessStatus {
Created, //!< The process was created but the main thread has not started yet
Started, //!< The process has been started
Exiting //!< The process is exiting
} status = ProcessStatus::Created; //!< The state of the process
handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle handle_t handleIndex = constant::BaseHandleIndex; //!< This is used to keep track of what to map as an handle
pid_t mainThread; //!< The PID of the main thread pid_t mainThread; //!< The PID of the main thread
size_t mainThreadStackSz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se) size_t mainThreadStackSz; //!< The size of the main thread's stack (All other threads map stack themselves so we don't know the size per-se)
std::map<u64, std::shared_ptr<KPrivateMemory>> memoryMap; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory, used to keep track of KPrivateMemory instances std::unordered_map<u64, std::shared_ptr<KPrivateMemory>> memoryMap; //!< A mapping from every address to a shared pointer of it's corresponding KPrivateMemory, used to keep track of KPrivateMemory instances
std::map<memory::Region, std::shared_ptr<KPrivateMemory>> memoryRegionMap; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory std::unordered_map<memory::Region, std::shared_ptr<KPrivateMemory>> memoryRegionMap; //!< A mapping from every MemoryRegion to a shared pointer of it's corresponding KPrivateMemory
std::map<handle_t, std::shared_ptr<KObject>> handleTable; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object std::unordered_map<handle_t, std::shared_ptr<KObject>> handleTable; //!< A mapping from a handle_t to it's corresponding KObject which is the actual underlying object
std::map<pid_t, std::shared_ptr<KThread>> threadMap; //!< A mapping from a PID to it's corresponding KThread object std::unordered_map<pid_t, std::shared_ptr<KThread>> threadMap; //!< A mapping from a PID to it's corresponding KThread object
std::unordered_map<u64, std::vector<std::shared_ptr<KThread>>> mutexMap; //!< A map from a mutex's address to a vector of threads waiting on it (Sorted by priority)
std::unordered_map<u64, std::vector<std::shared_ptr<KThread>>> condVarMap; //!< A map from a conditional variable's address to a vector of threads waiting on it (Sorted by priority)
std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages std::vector<std::shared_ptr<TlsPage>> tlsPages; //!< A vector of all allocated TLS pages
/** /**
@ -223,5 +229,17 @@ namespace skyline::kernel::type {
throw exception(fmt::format("GetHandle was called with invalid handle: 0x{:X}", handle)); throw exception(fmt::format("GetHandle was called with invalid handle: 0x{:X}", handle));
} }
} }
/**
* @brief This locks the Mutex at the specified address
* @param address The address of the mutex
*/
void MutexLock(u64 address);
/**
* @brief This unlocks the Mutex at the specified address
* @param address The address of the mutex
*/
void MutexUnlock(u64 address);
}; };
} }

View File

@ -13,10 +13,18 @@ namespace skyline::kernel::type {
u64 entryArg; //!< An argument to pass to the process on entry u64 entryArg; //!< An argument to pass to the process on entry
public: public:
enum class ThreadStatus { Created, Running, Sleeping, Waiting, Runnable } status = ThreadStatus::Created; //!< The state of the thread enum class ThreadStatus {
handle_t handle; // The handle of the object in the handle table Created, //!< The thread has been created but has not been started yet
Running, //!< The thread is running currently
Sleeping, //!< The thread is sleeping due to svcSleepThread
WaitSync, //!< The thread is waiting for a KSyncObject signal
WaitMutex, //!< The thread is waiting on a Mutex
WaitCondVar, //!< The thread is waiting on a Conditional Variable
Runnable //!< The thread is ready to run after waiting
} status = ThreadStatus::Created; //!< The state of the thread
std::vector<std::shared_ptr<KSyncObject>> waitObjects; //!< A vector holding handles this thread is waiting for std::vector<std::shared_ptr<KSyncObject>> waitObjects; //!< A vector holding handles this thread is waiting for
u64 timeoutEnd{}; //!< The time when a svcWaitSynchronization u64 timeout{}; //!< The end of a timeout for svcWaitSynchronization or the end of the sleep period for svcSleepThread
handle_t handle; // The handle of the object in the handle table
pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level]) pid_t pid; //!< The PID of the current thread (As in kernel PID and not PGID [In short, Linux implements threads as processes that share a lot of stuff at the kernel level])
u64 stackTop; //!< The top of the stack (Where it starts growing downwards from) u64 stackTop; //!< The top of the stack (Where it starts growing downwards from)
u64 tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread u64 tls; //!< The address of TLS (Thread Local Storage) slot assigned to the current thread

View File

@ -72,9 +72,9 @@ namespace skyline {
state->os->KillThread(currPid); state->os->KillThread(currPid);
} }
} }
} else if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Waiting || state->thisThread->status == kernel::type::KThread::ThreadStatus::Sleeping) { } else if (state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitSync || state->thisThread->status == kernel::type::KThread::ThreadStatus::Sleeping || state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitCondVar) {
if (state->thisThread->timeoutEnd >= std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now().time_since_epoch()).count()) { if (state->thisThread->timeout >= GetCurrTimeNs()) {
if (state->thisThread->status == kernel::type::KThread::ThreadStatus::Waiting) if (state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitSync || state->thisThread->status == kernel::type::KThread::ThreadStatus::WaitCondVar)
SetRegister(Wreg::W0, constant::status::Timeout); SetRegister(Wreg::W0, constant::status::Timeout);
state->thisThread->status = kernel::type::KThread::ThreadStatus::Runnable; state->thisThread->status = kernel::type::KThread::ThreadStatus::Runnable;
} }
@ -166,7 +166,7 @@ namespace skyline {
registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)] = value; registerMap.at(pid ? pid : currPid).regs[static_cast<uint>(regId)] = value;
} }
u64 NCE::GetRegister(Wreg regId, pid_t pid) { u32 NCE::GetRegister(Wreg regId, pid_t pid) {
return (reinterpret_cast<u32 *>(&registerMap.at(pid ? pid : currPid).regs))[static_cast<uint>(regId) * 2]; return (reinterpret_cast<u32 *>(&registerMap.at(pid ? pid : currPid).regs))[static_cast<uint>(regId) * 2];
} }

View File

@ -112,7 +112,7 @@ namespace skyline {
* @param pid The PID of the process (Defaults to currPid) * @param pid The PID of the process (Defaults to currPid)
* @return The value in the register * @return The value in the register
*/ */
u64 GetRegister(Wreg regId, pid_t pid = 0); u32 GetRegister(Wreg regId, pid_t pid = 0);
/** /**
* @brief Set the value of a Wn register * @brief Set the value of a Wn register