Merge pull request #3906 from NarcolepticK/cecd-migrate-framework
service/cecd: Migrate to ServiceFramework
This commit is contained in:
commit
945b3413a1
|
@ -3,64 +3,61 @@
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/hle/ipc.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/kernel/event.h"
|
|
||||||
#include "core/hle/kernel/handle_table.h"
|
|
||||||
#include "core/hle/result.h"
|
#include "core/hle/result.h"
|
||||||
#include "core/hle/service/cecd/cecd.h"
|
#include "core/hle/service/cecd/cecd.h"
|
||||||
#include "core/hle/service/cecd/cecd_ndm.h"
|
#include "core/hle/service/cecd/cecd_ndm.h"
|
||||||
#include "core/hle/service/cecd/cecd_s.h"
|
#include "core/hle/service/cecd/cecd_s.h"
|
||||||
#include "core/hle/service/cecd/cecd_u.h"
|
#include "core/hle/service/cecd/cecd_u.h"
|
||||||
#include "core/hle/service/service.h"
|
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
static Kernel::SharedPtr<Kernel::Event> cecinfo_event;
|
void Module::Interface::GetCecStateAbbreviated(Kernel::HLERequestContext& ctx) {
|
||||||
static Kernel::SharedPtr<Kernel::Event> change_state_event;
|
IPC::RequestParser rp(ctx, 0x0E, 0, 0);
|
||||||
|
|
||||||
void GetCecStateAbbreviated(Service::Interface* self) {
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
rb.PushEnum(CecStateAbbreviated::CEC_STATE_ABBREV_IDLE);
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
|
||||||
cmd_buff[2] = static_cast<u32>(CecStateAbbreviated::CEC_STATE_ABBREV_IDLE);
|
|
||||||
|
|
||||||
LOG_WARNING(Service_CECD, "(STUBBED) called");
|
LOG_WARNING(Service_CECD, "(STUBBED) called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetCecInfoEventHandle(Service::Interface* self) {
|
void Module::Interface::GetCecInfoEventHandle(Kernel::HLERequestContext& ctx) {
|
||||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
IPC::RequestParser rp(ctx, 0x0F, 0, 0);
|
||||||
|
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).Unwrap(); // Event handle
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
rb.PushCopyObjects(cecd->cecinfo_event);
|
||||||
|
|
||||||
LOG_WARNING(Service_CECD, "(STUBBED) called");
|
LOG_WARNING(Service_CECD, "(STUBBED) called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetChangeStateEventHandle(Service::Interface* self) {
|
void Module::Interface::GetChangeStateEventHandle(Kernel::HLERequestContext& ctx) {
|
||||||
u32* cmd_buff = Kernel::GetCommandBuffer();
|
IPC::RequestParser rp(ctx, 0x10, 0, 0);
|
||||||
|
|
||||||
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
|
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
|
||||||
cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).Unwrap(); // Event handle
|
rb.Push(RESULT_SUCCESS);
|
||||||
|
rb.PushCopyObjects(cecd->change_state_event);
|
||||||
|
|
||||||
LOG_WARNING(Service_CECD, "(STUBBED) called");
|
LOG_WARNING(Service_CECD, "(STUBBED) called");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Init() {
|
Module::Interface::Interface(std::shared_ptr<Module> cecd, const char* name, u32 max_session)
|
||||||
AddService(new CECD_NDM);
|
: ServiceFramework(name, max_session), cecd(std::move(cecd)) {}
|
||||||
AddService(new CECD_S);
|
|
||||||
AddService(new CECD_U);
|
|
||||||
|
|
||||||
cecinfo_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD::cecinfo_event");
|
Module::Module() {
|
||||||
change_state_event =
|
using namespace Kernel;
|
||||||
Kernel::Event::Create(Kernel::ResetType::OneShot, "CECD::change_state_event");
|
cecinfo_event = Event::Create(Kernel::ResetType::OneShot, "CECD::cecinfo_event");
|
||||||
|
change_state_event = Event::Create(Kernel::ResetType::OneShot, "CECD::change_state_event");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown() {
|
void InstallInterfaces(SM::ServiceManager& service_manager) {
|
||||||
cecinfo_event = nullptr;
|
auto cecd = std::make_shared<Module>();
|
||||||
change_state_event = nullptr;
|
std::make_shared<CECD_NDM>(cecd)->InstallAsService(service_manager);
|
||||||
|
std::make_shared<CECD_S>(cecd)->InstallAsService(service_manager);
|
||||||
|
std::make_shared<CECD_U>(cecd)->InstallAsService(service_manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
|
|
||||||
} // namespace Service
|
} // namespace Service
|
||||||
|
|
|
@ -4,13 +4,13 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/kernel/event.h"
|
||||||
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
|
|
||||||
class Interface;
|
|
||||||
|
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
enum class CecStateAbbreviated {
|
enum class CecStateAbbreviated : u32 {
|
||||||
CEC_STATE_ABBREV_IDLE = 1, ///< Corresponds to CEC_STATE_IDLE
|
CEC_STATE_ABBREV_IDLE = 1, ///< Corresponds to CEC_STATE_IDLE
|
||||||
CEC_STATE_ABBREV_NOT_LOCAL = 2, ///< Corresponds to CEC_STATEs *FINISH*, *POST, and OVER_BOSS
|
CEC_STATE_ABBREV_NOT_LOCAL = 2, ///< Corresponds to CEC_STATEs *FINISH*, *POST, and OVER_BOSS
|
||||||
CEC_STATE_ABBREV_SCANNING = 3, ///< Corresponds to CEC_STATE_SCANNING
|
CEC_STATE_ABBREV_SCANNING = 3, ///< Corresponds to CEC_STATE_SCANNING
|
||||||
|
@ -20,41 +20,58 @@ enum class CecStateAbbreviated {
|
||||||
/// OVER_BOSS and those listed here
|
/// OVER_BOSS and those listed here
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
class Module final {
|
||||||
* GetCecStateAbbreviated service function
|
public:
|
||||||
* Inputs:
|
Module();
|
||||||
* 0: 0x000E0000
|
~Module() = default;
|
||||||
* Outputs:
|
|
||||||
* 1: ResultCode
|
|
||||||
* 2: CecStateAbbreviated
|
|
||||||
*/
|
|
||||||
void GetCecStateAbbreviated(Service::Interface* self);
|
|
||||||
|
|
||||||
/**
|
class Interface : public ServiceFramework<Interface> {
|
||||||
* GetCecInfoEventHandle service function
|
public:
|
||||||
* Inputs:
|
Interface(std::shared_ptr<Module> cecd, const char* name, u32 max_session);
|
||||||
* 0: 0x000F0000
|
~Interface() = default;
|
||||||
* Outputs:
|
|
||||||
* 1: ResultCode
|
|
||||||
* 3: Event Handle
|
|
||||||
*/
|
|
||||||
void GetCecInfoEventHandle(Service::Interface* self);
|
|
||||||
|
|
||||||
/**
|
protected:
|
||||||
* GetChangeStateEventHandle service function
|
/**
|
||||||
* Inputs:
|
* GetCecStateAbbreviated service function
|
||||||
* 0: 0x00100000
|
* Inputs:
|
||||||
* Outputs:
|
* 0: 0x000E0000
|
||||||
* 1: ResultCode
|
* Outputs:
|
||||||
* 3: Event Handle
|
* 1: ResultCode
|
||||||
*/
|
* 2: CecStateAbbreviated
|
||||||
void GetChangeStateEventHandle(Service::Interface* self);
|
*/
|
||||||
|
void GetCecStateAbbreviated(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetCecInfoEventHandle service function
|
||||||
|
* Inputs:
|
||||||
|
* 0: 0x000F0000
|
||||||
|
* Outputs:
|
||||||
|
* 1: ResultCode
|
||||||
|
* 3: Event Handle
|
||||||
|
*/
|
||||||
|
void GetCecInfoEventHandle(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetChangeStateEventHandle service function
|
||||||
|
* Inputs:
|
||||||
|
* 0: 0x00100000
|
||||||
|
* Outputs:
|
||||||
|
* 1: ResultCode
|
||||||
|
* 3: Event Handle
|
||||||
|
*/
|
||||||
|
void GetChangeStateEventHandle(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<Module> cecd;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
Kernel::SharedPtr<Kernel::Event> cecinfo_event;
|
||||||
|
Kernel::SharedPtr<Kernel::Event> change_state_event;
|
||||||
|
};
|
||||||
|
|
||||||
/// Initialize CECD service(s)
|
/// Initialize CECD service(s)
|
||||||
void Init();
|
void InstallInterfaces(SM::ServiceManager& service_manager);
|
||||||
|
|
||||||
/// Shutdown CECD service(s)
|
|
||||||
void Shutdown();
|
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
} // namespace Service
|
} // namespace Service
|
||||||
|
|
|
@ -2,21 +2,23 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "core/hle/service/cecd/cecd.h"
|
|
||||||
#include "core/hle/service/cecd/cecd_ndm.h"
|
#include "core/hle/service/cecd/cecd_ndm.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
static const Interface::FunctionInfo FunctionTable[] = {
|
CECD_NDM::CECD_NDM(std::shared_ptr<Module> cecd)
|
||||||
{0x00010000, nullptr, "Initialize"},
|
: Module::Interface(std::move(cecd), "cecd:ndm", DefaultMaxSessions) {
|
||||||
{0x00020000, nullptr, "Deinitialize"},
|
static const FunctionInfo functions[] = {
|
||||||
{0x00030000, nullptr, "ResumeDaemon"},
|
// clang-format off
|
||||||
{0x00040040, nullptr, "SuspendDaemon"},
|
{0x00010000, nullptr, "Initialize"},
|
||||||
};
|
{0x00020000, nullptr, "Deinitialize"},
|
||||||
|
{0x00030000, nullptr, "ResumeDaemon"},
|
||||||
|
{0x00040040, nullptr, "SuspendDaemon"},
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
CECD_NDM::CECD_NDM() {
|
RegisterHandlers(functions);
|
||||||
Register(FunctionTable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
|
|
|
@ -4,18 +4,14 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/cecd/cecd.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
class CECD_NDM : public Interface {
|
class CECD_NDM final : public Module::Interface {
|
||||||
public:
|
public:
|
||||||
CECD_NDM();
|
explicit CECD_NDM(std::shared_ptr<Module> cecd);
|
||||||
|
|
||||||
std::string GetPortName() const override {
|
|
||||||
return "cecd:ndm";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
|
|
|
@ -2,34 +2,36 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "core/hle/service/cecd/cecd.h"
|
|
||||||
#include "core/hle/service/cecd/cecd_s.h"
|
#include "core/hle/service/cecd/cecd_s.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
static const Interface::FunctionInfo FunctionTable[] = {
|
CECD_S::CECD_S(std::shared_ptr<Module> cecd)
|
||||||
// cecd:u shared commands
|
: Module::Interface(std::move(cecd), "cecd:s", DefaultMaxSessions) {
|
||||||
{0x000100C2, nullptr, "OpenRawFile"},
|
static const FunctionInfo functions[] = {
|
||||||
{0x00020042, nullptr, "ReadRawFile"},
|
// cecd:u shared commands
|
||||||
{0x00030104, nullptr, "ReadMessage"},
|
// clang-format off
|
||||||
{0x00040106, nullptr, "ReadMessageWithHMAC"},
|
{0x000100C2, nullptr, "OpenRawFile"},
|
||||||
{0x00050042, nullptr, "WriteRawFile"},
|
{0x00020042, nullptr, "ReadRawFile"},
|
||||||
{0x00060104, nullptr, "WriteMessage"},
|
{0x00030104, nullptr, "ReadMessage"},
|
||||||
{0x00070106, nullptr, "WriteMessageWithHMAC"},
|
{0x00040106, nullptr, "ReadMessageWithHMAC"},
|
||||||
{0x00080102, nullptr, "Delete"},
|
{0x00050042, nullptr, "WriteRawFile"},
|
||||||
{0x000A00C4, nullptr, "GetSystemInfo"},
|
{0x00060104, nullptr, "WriteMessage"},
|
||||||
{0x000B0040, nullptr, "RunCommand"},
|
{0x00070106, nullptr, "WriteMessageWithHMAC"},
|
||||||
{0x000C0040, nullptr, "RunCommandAlt"},
|
{0x00080102, nullptr, "Delete"},
|
||||||
{0x000E0000, GetCecStateAbbreviated, "GetCecStateAbbreviated"},
|
{0x000A00C4, nullptr, "GetSystemInfo"},
|
||||||
{0x000F0000, GetCecInfoEventHandle, "GetCecInfoEventHandle"},
|
{0x000B0040, nullptr, "RunCommand"},
|
||||||
{0x00100000, GetChangeStateEventHandle, "GetChangeStateEventHandle"},
|
{0x000C0040, nullptr, "RunCommandAlt"},
|
||||||
{0x00110104, nullptr, "OpenAndWrite"},
|
{0x000E0000, &CECD_S::GetCecStateAbbreviated, "GetCecStateAbbreviated"},
|
||||||
{0x00120104, nullptr, "OpenAndRead"},
|
{0x000F0000, &CECD_S::GetCecInfoEventHandle, "GetCecInfoEventHandle"},
|
||||||
};
|
{0x00100000, &CECD_S::GetChangeStateEventHandle, "GetChangeStateEventHandle"},
|
||||||
|
{0x00110104, nullptr, "OpenAndWrite"},
|
||||||
|
{0x00120104, nullptr, "OpenAndRead"},
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
CECD_S::CECD_S() {
|
RegisterHandlers(functions);
|
||||||
Register(FunctionTable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
|
|
|
@ -4,18 +4,14 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/cecd/cecd.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
class CECD_S : public Interface {
|
class CECD_S final : public Module::Interface {
|
||||||
public:
|
public:
|
||||||
CECD_S();
|
explicit CECD_S(std::shared_ptr<Module> cecd);
|
||||||
|
|
||||||
std::string GetPortName() const override {
|
|
||||||
return "cecd:s";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
|
|
|
@ -2,34 +2,36 @@
|
||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "core/hle/service/cecd/cecd.h"
|
|
||||||
#include "core/hle/service/cecd/cecd_u.h"
|
#include "core/hle/service/cecd/cecd_u.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
static const Interface::FunctionInfo FunctionTable[] = {
|
CECD_U::CECD_U(std::shared_ptr<Module> cecd)
|
||||||
// cecd:u shared commands
|
: Module::Interface(std::move(cecd), "cecd:u", DefaultMaxSessions) {
|
||||||
{0x000100C2, nullptr, "OpenRawFile"},
|
static const FunctionInfo functions[] = {
|
||||||
{0x00020042, nullptr, "ReadRawFile"},
|
// cecd:u shared commands
|
||||||
{0x00030104, nullptr, "ReadMessage"},
|
// clang-format off
|
||||||
{0x00040106, nullptr, "ReadMessageWithHMAC"},
|
{0x000100C2, nullptr, "OpenRawFile"},
|
||||||
{0x00050042, nullptr, "WriteRawFile"},
|
{0x00020042, nullptr, "ReadRawFile"},
|
||||||
{0x00060104, nullptr, "WriteMessage"},
|
{0x00030104, nullptr, "ReadMessage"},
|
||||||
{0x00070106, nullptr, "WriteMessageWithHMAC"},
|
{0x00040106, nullptr, "ReadMessageWithHMAC"},
|
||||||
{0x00080102, nullptr, "Delete"},
|
{0x00050042, nullptr, "WriteRawFile"},
|
||||||
{0x000A00C4, nullptr, "GetSystemInfo"},
|
{0x00060104, nullptr, "WriteMessage"},
|
||||||
{0x000B0040, nullptr, "RunCommand"},
|
{0x00070106, nullptr, "WriteMessageWithHMAC"},
|
||||||
{0x000C0040, nullptr, "RunCommandAlt"},
|
{0x00080102, nullptr, "Delete"},
|
||||||
{0x000E0000, GetCecStateAbbreviated, "GetCecStateAbbreviated"},
|
{0x000A00C4, nullptr, "GetSystemInfo"},
|
||||||
{0x000F0000, GetCecInfoEventHandle, "GetCecInfoEventHandle"},
|
{0x000B0040, nullptr, "RunCommand"},
|
||||||
{0x00100000, GetChangeStateEventHandle, "GetChangeStateEventHandle"},
|
{0x000C0040, nullptr, "RunCommandAlt"},
|
||||||
{0x00110104, nullptr, "OpenAndWrite"},
|
{0x000E0000, &CECD_U::GetCecStateAbbreviated, "GetCecStateAbbreviated"},
|
||||||
{0x00120104, nullptr, "OpenAndRead"},
|
{0x000F0000, &CECD_U::GetCecInfoEventHandle, "GetCecInfoEventHandle"},
|
||||||
};
|
{0x00100000, &CECD_U::GetChangeStateEventHandle, "GetChangeStateEventHandle"},
|
||||||
|
{0x00110104, nullptr, "OpenAndWrite"},
|
||||||
|
{0x00120104, nullptr, "OpenAndRead"},
|
||||||
|
// clang-format on
|
||||||
|
};
|
||||||
|
|
||||||
CECD_U::CECD_U() {
|
RegisterHandlers(functions);
|
||||||
Register(FunctionTable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
|
|
|
@ -4,18 +4,14 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/cecd/cecd.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace CECD {
|
namespace CECD {
|
||||||
|
|
||||||
class CECD_U : public Interface {
|
class CECD_U final : public Module::Interface {
|
||||||
public:
|
public:
|
||||||
CECD_U();
|
explicit CECD_U(std::shared_ptr<Module> cecd);
|
||||||
|
|
||||||
std::string GetPortName() const override {
|
|
||||||
return "cecd:u";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace CECD
|
} // namespace CECD
|
||||||
|
|
|
@ -239,7 +239,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
|
||||||
APT::InstallInterfaces(*sm);
|
APT::InstallInterfaces(*sm);
|
||||||
BOSS::Init();
|
BOSS::Init();
|
||||||
CAM::InstallInterfaces(*sm);
|
CAM::InstallInterfaces(*sm);
|
||||||
CECD::Init();
|
CECD::InstallInterfaces(*sm);
|
||||||
CFG::InstallInterfaces(*sm);
|
CFG::InstallInterfaces(*sm);
|
||||||
DLP::InstallInterfaces(*sm);
|
DLP::InstallInterfaces(*sm);
|
||||||
FRD::InstallInterfaces(*sm);
|
FRD::InstallInterfaces(*sm);
|
||||||
|
@ -268,7 +268,6 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm) {
|
||||||
|
|
||||||
/// Shutdown ServiceManager
|
/// Shutdown ServiceManager
|
||||||
void Shutdown() {
|
void Shutdown() {
|
||||||
CECD::Shutdown();
|
|
||||||
BOSS::Shutdown();
|
BOSS::Shutdown();
|
||||||
FS::ArchiveShutdown();
|
FS::ArchiveShutdown();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue