core: gpu interrupt dispatcher
This commit is contained in:
parent
581688c1ac
commit
923baf0164
|
@ -7,6 +7,7 @@
|
||||||
#include "core/libraries/gnmdriver/gnmdriver.h"
|
#include "core/libraries/gnmdriver/gnmdriver.h"
|
||||||
#include "core/libraries/libs.h"
|
#include "core/libraries/libs.h"
|
||||||
#include "core/libraries/videoout/video_out.h"
|
#include "core/libraries/videoout/video_out.h"
|
||||||
|
#include "core/platform.h"
|
||||||
#include "video_core/amdgpu/liverpool.h"
|
#include "video_core/amdgpu/liverpool.h"
|
||||||
#include "video_core/amdgpu/pm4_cmds.h"
|
#include "video_core/amdgpu/pm4_cmds.h"
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
@ -48,8 +49,12 @@ s32 PS4_SYSV_ABI sceGnmAddEqEvent(SceKernelEqueue eq, u64 id, void* udata) {
|
||||||
kernel_event.event.udata = udata;
|
kernel_event.event.udata = udata;
|
||||||
eq->addEvent(kernel_event);
|
eq->addEvent(kernel_event);
|
||||||
|
|
||||||
liverpool->SetEopCallback(
|
Platform::IrqC::Instance()->Register([=](Platform::InterruptId irq) {
|
||||||
[=]() { eq->triggerEvent(SceKernelEvent::Type::GfxEop, EVFILT_GRAPHICS_CORE, nullptr); });
|
ASSERT_MSG(irq == Platform::InterruptId::GfxEop,
|
||||||
|
"An unexpected IRQ occured"); // We need to conver IRQ# to event id and do proper
|
||||||
|
// filtering in trigger function
|
||||||
|
eq->triggerEvent(SceKernelEvent::Type::GfxEop, EVFILT_GRAPHICS_CORE, nullptr);
|
||||||
|
});
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,6 +163,8 @@ s32 PS4_SYSV_ABI sceGnmDeleteEqEvent(SceKernelEqueue eq, u64 id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
eq->removeEvent(id);
|
eq->removeEvent(id);
|
||||||
|
|
||||||
|
Platform::IrqC::Instance()->Unregister();
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1356,7 +1363,7 @@ s32 PS4_SYSV_ABI sceGnmSubmitAndFlipCommandBuffers(u32 count, void* dcb_gpu_addr
|
||||||
u32* dcb_sizes_in_bytes, void* ccb_gpu_addrs[],
|
u32* dcb_sizes_in_bytes, void* ccb_gpu_addrs[],
|
||||||
u32* ccb_sizes_in_bytes, u32 vo_handle,
|
u32* ccb_sizes_in_bytes, u32 vo_handle,
|
||||||
u32 buf_idx, u32 flip_mode, u32 flip_arg) {
|
u32 buf_idx, u32 flip_mode, u32 flip_arg) {
|
||||||
LOG_INFO(Lib_GnmDriver, "called");
|
LOG_INFO(Lib_GnmDriver, "called [buf = {}]", buf_idx);
|
||||||
|
|
||||||
auto* cmdbuf = reinterpret_cast<u32*>(dcb_gpu_addrs[count - 1]);
|
auto* cmdbuf = reinterpret_cast<u32*>(dcb_gpu_addrs[count - 1]);
|
||||||
const auto size_dw = dcb_sizes_in_bytes[count - 1] / 4;
|
const auto size_dw = dcb_sizes_in_bytes[count - 1] / 4;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "core/libraries/error_codes.h"
|
#include "core/libraries/error_codes.h"
|
||||||
#include "core/libraries/kernel/time_management.h"
|
#include "core/libraries/kernel/time_management.h"
|
||||||
#include "core/libraries/videoout/driver.h"
|
#include "core/libraries/videoout/driver.h"
|
||||||
|
#include "core/platform.h"
|
||||||
|
|
||||||
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
#include "video_core/renderer_vulkan/renderer_vulkan.h"
|
||||||
|
|
||||||
|
@ -199,16 +200,19 @@ void VideoOutDriver::Flip(std::chrono::microseconds timeout) {
|
||||||
|
|
||||||
// Reset flip label
|
// Reset flip label
|
||||||
req.port->buffer_labels[req.index] = 0;
|
req.port->buffer_labels[req.index] = 0;
|
||||||
|
LOG_INFO(Lib_VideoOut, "Flip done [buf = {}]", req.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg) {
|
bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg,
|
||||||
|
bool is_eop /*= false*/) {
|
||||||
const auto& buffer = port->buffer_slots[index];
|
const auto& buffer = port->buffer_slots[index];
|
||||||
const auto& group = port->groups[buffer.group_index];
|
const auto& group = port->groups[buffer.group_index];
|
||||||
auto* frame = renderer->PrepareFrame(group, buffer.address_left);
|
auto* frame = renderer->PrepareFrame(group, buffer.address_left);
|
||||||
|
|
||||||
std::scoped_lock lock{mutex};
|
std::scoped_lock lock{mutex};
|
||||||
|
|
||||||
if (requests.size() >= 2) {
|
if (requests.size() >= port->NumRegisteredBuffers()) {
|
||||||
|
LOG_ERROR(Lib_VideoOut, "Flip queue is full");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,6 +222,7 @@ bool VideoOutDriver::SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg) {
|
||||||
.index = index,
|
.index = index,
|
||||||
.flip_arg = flip_arg,
|
.flip_arg = flip_arg,
|
||||||
.submit_tsc = Libraries::Kernel::sceKernelReadTsc(),
|
.submit_tsc = Libraries::Kernel::sceKernelReadTsc(),
|
||||||
|
.eop = is_eop,
|
||||||
});
|
});
|
||||||
|
|
||||||
port->flip_status.flipPendingNum = static_cast<int>(requests.size());
|
port->flip_status.flipPendingNum = static_cast<int>(requests.size());
|
||||||
|
|
|
@ -34,6 +34,11 @@ struct VideoOutPort {
|
||||||
}
|
}
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] int NumRegisteredBuffers() const {
|
||||||
|
return std::count_if(buffer_slots.cbegin(), buffer_slots.cend(),
|
||||||
|
[](auto& buffer) { return buffer.group_index != -1; });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ServiceThreadParams {
|
struct ServiceThreadParams {
|
||||||
|
@ -59,7 +64,7 @@ public:
|
||||||
int UnregisterBuffers(VideoOutPort* port, s32 attributeIndex);
|
int UnregisterBuffers(VideoOutPort* port, s32 attributeIndex);
|
||||||
|
|
||||||
void Flip(std::chrono::microseconds timeout);
|
void Flip(std::chrono::microseconds timeout);
|
||||||
bool SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg);
|
bool SubmitFlip(VideoOutPort* port, s32 index, s64 flip_arg, bool is_eop = false);
|
||||||
|
|
||||||
void Vblank();
|
void Vblank();
|
||||||
|
|
||||||
|
@ -70,6 +75,7 @@ private:
|
||||||
s32 index;
|
s32 index;
|
||||||
s64 flip_arg;
|
s64 flip_arg;
|
||||||
u64 submit_tsc;
|
u64 submit_tsc;
|
||||||
|
bool eop;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "core/libraries/videoout/driver.h"
|
#include "core/libraries/videoout/driver.h"
|
||||||
#include "core/libraries/videoout/video_out.h"
|
#include "core/libraries/videoout/video_out.h"
|
||||||
#include "core/loader/symbols_resolver.h"
|
#include "core/loader/symbols_resolver.h"
|
||||||
|
#include "core/platform.h"
|
||||||
|
|
||||||
namespace Libraries::VideoOut {
|
namespace Libraries::VideoOut {
|
||||||
|
|
||||||
|
@ -216,13 +217,17 @@ void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr) {
|
||||||
*label_addr = reinterpret_cast<uintptr_t>(port->buffer_labels.data());
|
*label_addr = reinterpret_cast<uintptr_t>(port->buffer_labels.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void* unk) {
|
s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk) {
|
||||||
auto* port = driver->GetPort(handle);
|
auto* port = driver->GetPort(handle);
|
||||||
if (!port) {
|
if (!port) {
|
||||||
return 0x8029000b;
|
return 0x8029000b;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
Platform::IrqC::Instance()->RegisterOnce([=](Platform::InterruptId irq) {
|
||||||
|
ASSERT_MSG(irq == Platform::InterruptId::GfxEop, "An unexpected IRQ occured");
|
||||||
|
const auto result = driver->SubmitFlip(port, buf_id, arg, true);
|
||||||
|
ASSERT_MSG(result, "EOP flip submission failed");
|
||||||
|
});
|
||||||
|
|
||||||
return ORBIS_OK;
|
return ORBIS_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ void Vblank();
|
||||||
|
|
||||||
// Internal system functions
|
// Internal system functions
|
||||||
void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr);
|
void sceVideoOutGetBufferLabelAddress(s32 handle, uintptr_t* label_addr);
|
||||||
s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void* unk);
|
s32 sceVideoOutSubmitEopFlip(s32 handle, u32 buf_id, u32 mode, u32 arg, void** unk);
|
||||||
|
|
||||||
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
void RegisterLib(Core::Loader::SymbolsResolver* sym);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,78 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/singleton.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "magic_enum.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
|
namespace Platform {
|
||||||
|
|
||||||
|
enum class InterruptId : u32 {
|
||||||
|
Compute0RelMem = 0u,
|
||||||
|
Compute1RelMem = 1u,
|
||||||
|
Compute2RelMem = 2u,
|
||||||
|
Compute3RelMem = 3u,
|
||||||
|
Compute4RelMem = 4u,
|
||||||
|
Compute5RelMem = 5u,
|
||||||
|
Compute6RelMem = 6u,
|
||||||
|
GfxEop = 0x40u
|
||||||
|
};
|
||||||
|
|
||||||
|
using IrqHandler = std::function<void(InterruptId)>;
|
||||||
|
|
||||||
|
struct IrqController {
|
||||||
|
void RegisterOnce(IrqHandler handler) {
|
||||||
|
std::unique_lock lock{m_lock};
|
||||||
|
one_time_subscribers.emplace(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Register(IrqHandler handler) {
|
||||||
|
ASSERT_MSG(!persistent_handler.has_value(),
|
||||||
|
"Too many persistent handlers"); // Add a slot map if so
|
||||||
|
{
|
||||||
|
std::unique_lock lock{m_lock};
|
||||||
|
persistent_handler.emplace(handler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unregister() {
|
||||||
|
std::unique_lock lock{m_lock};
|
||||||
|
persistent_handler.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Signal(InterruptId irq) {
|
||||||
|
LOG_TRACE(Core, "IRQ signaled: {}", magic_enum::enum_name(irq));
|
||||||
|
{
|
||||||
|
std::unique_lock lock{m_lock};
|
||||||
|
|
||||||
|
if (persistent_handler) {
|
||||||
|
persistent_handler.value()(irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!one_time_subscribers.empty()) {
|
||||||
|
const auto& h = one_time_subscribers.front();
|
||||||
|
h(irq);
|
||||||
|
|
||||||
|
one_time_subscribers.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::optional<IrqHandler> persistent_handler{};
|
||||||
|
std::queue<IrqHandler> one_time_subscribers{};
|
||||||
|
std::mutex m_lock{};
|
||||||
|
};
|
||||||
|
|
||||||
|
using IrqC = Common::Singleton<IrqController>;
|
||||||
|
|
||||||
|
} // namespace Platform
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/io_file.h"
|
#include "common/io_file.h"
|
||||||
|
#include "common/thread.h"
|
||||||
#include "video_core/amdgpu/liverpool.h"
|
#include "video_core/amdgpu/liverpool.h"
|
||||||
#include "video_core/amdgpu/pm4_cmds.h"
|
#include "video_core/amdgpu/pm4_cmds.h"
|
||||||
|
|
||||||
|
@ -11,6 +12,8 @@ namespace AmdGpu {
|
||||||
Liverpool::Liverpool() = default;
|
Liverpool::Liverpool() = default;
|
||||||
|
|
||||||
void Liverpool::ProcessCmdList(u32* cmdbuf, u32 size_in_bytes) {
|
void Liverpool::ProcessCmdList(u32* cmdbuf, u32 size_in_bytes) {
|
||||||
|
Common::SetCurrentThreadName("CommandProcessor_Gfx");
|
||||||
|
|
||||||
auto* header = reinterpret_cast<PM4Header*>(cmdbuf);
|
auto* header = reinterpret_cast<PM4Header*>(cmdbuf);
|
||||||
u32 processed_cmd_size = 0;
|
u32 processed_cmd_size = 0;
|
||||||
|
|
||||||
|
@ -70,54 +73,12 @@ void Liverpool::ProcessCmdList(u32* cmdbuf, u32 size_in_bytes) {
|
||||||
}
|
}
|
||||||
case PM4ItOpcode::EventWriteEos: {
|
case PM4ItOpcode::EventWriteEos: {
|
||||||
const auto* event_eos = reinterpret_cast<PM4CmdEventWriteEos*>(header);
|
const auto* event_eos = reinterpret_cast<PM4CmdEventWriteEos*>(header);
|
||||||
switch (event_eos->command.Value()) {
|
event_eos->SignalFence();
|
||||||
case PM4CmdEventWriteEos::Command::SingalFence: {
|
|
||||||
event_eos->SignalFence();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PM4ItOpcode::EventWriteEop: {
|
case PM4ItOpcode::EventWriteEop: {
|
||||||
const auto* event_eop = reinterpret_cast<PM4CmdEventWriteEop*>(header);
|
const auto* event_eop = reinterpret_cast<PM4CmdEventWriteEop*>(header);
|
||||||
const InterruptSelect irq_sel = event_eop->int_sel;
|
event_eop->SignalFence();
|
||||||
const DataSelect data_sel = event_eop->data_sel;
|
|
||||||
|
|
||||||
// Write back data if required
|
|
||||||
switch (data_sel) {
|
|
||||||
case DataSelect::Data32Low: {
|
|
||||||
*reinterpret_cast<u32*>(event_eop->Address()) = event_eop->DataDWord();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DataSelect::Data64: {
|
|
||||||
*event_eop->Address() = event_eop->DataQWord();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (irq_sel) {
|
|
||||||
case InterruptSelect::None: {
|
|
||||||
// No interrupt
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case InterruptSelect::IrqWhenWriteConfirm: {
|
|
||||||
if (eop_callback) {
|
|
||||||
eop_callback();
|
|
||||||
} else {
|
|
||||||
UNREACHABLE_MSG("EOP callback is not registered");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case PM4ItOpcode::DmaData: {
|
case PM4ItOpcode::DmaData: {
|
||||||
|
@ -143,11 +104,9 @@ void Liverpool::ProcessCmdList(u32* cmdbuf, u32 size_in_bytes) {
|
||||||
case PM4ItOpcode::WaitRegMem: {
|
case PM4ItOpcode::WaitRegMem: {
|
||||||
const auto* wait_reg_mem = reinterpret_cast<PM4CmdWaitRegMem*>(header);
|
const auto* wait_reg_mem = reinterpret_cast<PM4CmdWaitRegMem*>(header);
|
||||||
ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me);
|
ASSERT(wait_reg_mem->engine.Value() == PM4CmdWaitRegMem::Engine::Me);
|
||||||
ASSERT(wait_reg_mem->function.Value() == PM4CmdWaitRegMem::Function::Equal);
|
while (!wait_reg_mem->Test()) {
|
||||||
|
using namespace std::chrono_literals;
|
||||||
{
|
std::this_thread::sleep_for(1ms);
|
||||||
std::unique_lock lock{m_reg_mem};
|
|
||||||
cv_reg_mem.wait(lock, [&]() { return wait_reg_mem->Test(); });
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -624,17 +624,11 @@ public:
|
||||||
// reworked with mutiple queues introduction
|
// reworked with mutiple queues introduction
|
||||||
cp.get();
|
cp.get();
|
||||||
}
|
}
|
||||||
void SetEopCallback(auto const& cb) {
|
|
||||||
eop_callback = cb;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void ProcessCmdList(u32* cmdbuf, u32 size_in_bytes);
|
void ProcessCmdList(u32* cmdbuf, u32 size_in_bytes);
|
||||||
|
|
||||||
std::function<void(void)> eop_callback{};
|
|
||||||
std::future<void> cp{};
|
std::future<void> cp{};
|
||||||
std::condition_variable cv_reg_mem{};
|
|
||||||
std::mutex m_reg_mem{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(GFX6_3D_REG_INDEX(ps_program) == 0x2C08);
|
static_assert(GFX6_3D_REG_INDEX(ps_program) == 0x2C08);
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
#include "core/platform.h"
|
||||||
#include "video_core/amdgpu/pm4_opcodes.h"
|
#include "video_core/amdgpu/pm4_opcodes.h"
|
||||||
|
|
||||||
namespace AmdGpu {
|
namespace AmdGpu {
|
||||||
|
@ -282,8 +283,9 @@ struct PM4CmdEventWriteEop {
|
||||||
u32 data_lo; ///< Value that will be written to memory when event occurs
|
u32 data_lo; ///< Value that will be written to memory when event occurs
|
||||||
u32 data_hi; ///< Value that will be written to memory when event occurs
|
u32 data_hi; ///< Value that will be written to memory when event occurs
|
||||||
|
|
||||||
u64* Address() const {
|
template <typename T>
|
||||||
return reinterpret_cast<u64*>(address_lo | u64(address_hi) << 32);
|
T* Address() const {
|
||||||
|
return reinterpret_cast<T*>(address_lo | u64(address_hi) << 32);
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 DataDWord() const {
|
u32 DataDWord() const {
|
||||||
|
@ -293,6 +295,36 @@ struct PM4CmdEventWriteEop {
|
||||||
u64 DataQWord() const {
|
u64 DataQWord() const {
|
||||||
return data_lo | u64(data_hi) << 32;
|
return data_lo | u64(data_hi) << 32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SignalFence() const {
|
||||||
|
switch (data_sel.Value()) {
|
||||||
|
case DataSelect::Data32Low: {
|
||||||
|
*Address<u32>() = DataDWord();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DataSelect::Data64: {
|
||||||
|
*Address<u64>() = DataQWord();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (int_sel.Value()) {
|
||||||
|
case InterruptSelect::None: {
|
||||||
|
// No interrupt
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InterruptSelect::IrqWhenWriteConfirm: {
|
||||||
|
Platform::IrqC::Instance()->Signal(Platform::InterruptId::GfxEop);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PM4DmaData {
|
struct PM4DmaData {
|
||||||
|
@ -434,8 +466,15 @@ struct PM4CmdEventWriteEos {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SignalFence() const {
|
void SignalFence() const {
|
||||||
ASSERT_MSG(command.Value() == Command::SingalFence, "Invalid action on packet");
|
switch (command.Value()) {
|
||||||
*Address() = DataDWord();
|
case Command::SingalFence: {
|
||||||
|
*Address() = DataDWord();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue