From ab2568ddfb3eb878aaa148f815a6a20fa67bc7dc Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Sat, 29 Aug 2020 18:28:13 -0700 Subject: [PATCH] kern: add most of 1.x lps driver --- .../nintendo/nx/kern_atomics_registers.hpp | 21 + .../board/nintendo/nx/kern_bpmp_api.hpp | 63 +++ .../board/nintendo/nx/kern_flow_registers.hpp | 20 + .../nintendo/nx/kern_ictlr_registers.hpp | 25 ++ .../board/nintendo/nx/kern_lps_driver.cpp | 368 +++++++++++++++++- .../board/nintendo/nx/kern_lps_driver.hpp | 2 +- .../board/nintendo/nx/kern_sema_registers.hpp | 20 + 7 files changed, 514 insertions(+), 5 deletions(-) create mode 100644 libraries/libmesosphere/source/board/nintendo/nx/kern_atomics_registers.hpp create mode 100644 libraries/libmesosphere/source/board/nintendo/nx/kern_bpmp_api.hpp create mode 100644 libraries/libmesosphere/source/board/nintendo/nx/kern_flow_registers.hpp create mode 100644 libraries/libmesosphere/source/board/nintendo/nx/kern_ictlr_registers.hpp create mode 100644 libraries/libmesosphere/source/board/nintendo/nx/kern_sema_registers.hpp diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_atomics_registers.hpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_atomics_registers.hpp new file mode 100644 index 000000000..ee6564f59 --- /dev/null +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_atomics_registers.hpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 this program. If not, see . + */ +#pragma once + +#define ATOMICS_AP0_TRIGGER 0x000 +#define ATOMICS_AP0_RESULT(id) (0xc00 + id * 4) + +#define TRIGGER_CMD_GET 4 diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_bpmp_api.hpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_bpmp_api.hpp new file mode 100644 index 000000000..38c5b0796 --- /dev/null +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_bpmp_api.hpp @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 this program. If not, see . + */ +#pragma once +#include + +/* Message Flags */ +#define BPMP_MSG_DO_ACK (1 << 0) +#define BPMP_MSG_RING_DOORBELL (1 << 1) + +/* Messages */ +#define MRQ_PING 0 +#define MRQ_ENABLE_SUSPEND 17 +#define MRQ_CPU_PMIC_SELECT 28 + +/* BPMP Power states. */ +#define TEGRA_BPMP_PM_CC1 9 +#define TEGRA_BPMP_PM_CC4 12 +#define TEGRA_BPMP_PM_CC6 14 +#define TEGRA_BPMP_PM_CC7 15 +#define TEGRA_BPMP_PM_SC1 17 +#define TEGRA_BPMP_PM_SC2 18 +#define TEGRA_BPMP_PM_SC3 19 +#define TEGRA_BPMP_PM_SC4 20 +#define TEGRA_BPMP_PM_SC7 23 + +/* Channel states. */ +#define CH_MASK(ch) (0x3u << ((ch) * 2)) +#define SL_SIGL(ch) (0x0u << ((ch) * 2)) +#define SL_QUED(ch) (0x1u << ((ch) * 2)) +#define MA_FREE(ch) (0x2u << ((ch) * 2)) +#define MA_ACKD(ch) (0x3u << ((ch) * 2)) + +constexpr inline int MessageSize = 0x80; +constexpr inline int MessageDataSizeMax = 0x78; + +struct MailboxData { + s32 code; + s32 flags; + u8 data[MessageDataSizeMax]; +}; + +static_assert(ams::util::is_pod::value); +static_assert(sizeof(MailboxData) == MessageSize); + +struct ChannelData { + MailboxData *ib; + MailboxData *ob; +}; + + diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_flow_registers.hpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_flow_registers.hpp new file mode 100644 index 000000000..373454499 --- /dev/null +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_flow_registers.hpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 this program. If not, see . + */ +#pragma once + +#define FLOW_CTLR_CC4_HVC_CONTROL 0x060 +#define FLOW_CTLR_CC4_RETENTION_CONTROL 0x064 +#define FLOW_CTLR_CC4_HVC_RETRY 0x08C diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_ictlr_registers.hpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_ictlr_registers.hpp new file mode 100644 index 000000000..35623ea45 --- /dev/null +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_ictlr_registers.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 this program. If not, see . + */ +#pragma once + +#define ICTLR_REG_BASE(irq) ((((irq) - 32) >> 5) * 0x100) +#define ICTLR_FIR_SET(irq) (ICTLR_REG_BASE(irq) + 0x18) +#define ICTLR_FIR_CLR(irq) (ICTLR_REG_BASE(irq) + 0x1c) +#define FIR_BIT(irq) (1 << ((irq) & 0x1f)) + +#define INT_GIC_BASE (0) +#define INT_PRI_BASE (INT_GIC_BASE + 32) +#define INT_SHR_SEM_OUTBOX_IBF (INT_PRI_BASE + 6) diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp index 74c3b8ab7..9981771ae 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.cpp @@ -17,24 +17,379 @@ #include "kern_lps_driver.hpp" #include "kern_k_sleep_manager.hpp" +#include "kern_bpmp_api.hpp" +#include "kern_atomics_registers.hpp" +#include "kern_ictlr_registers.hpp" +#include "kern_flow_registers.hpp" +#include "kern_sema_registers.hpp" + namespace ams::kern::board::nintendo::nx::lps { namespace { - constinit bool g_lps_init_done = false; + constexpr inline int ChannelCount = 12; + + constexpr inline TimeSpan ChannelTimeout = TimeSpan::FromMicroSeconds(1); + + constinit bool g_lps_init_done = false; + constinit bool g_bpmp_connected = false; + constinit bool g_bpmp_mail_initialized = false; + + constinit KSpinLock g_bpmp_mrq_lock; + + constinit KVirtualAddress g_evp_address = Null; + constinit KVirtualAddress g_flow_address = Null; + constinit KVirtualAddress g_prictlr_address = Null; + constinit KVirtualAddress g_sema_address = Null; + constinit KVirtualAddress g_atomics_address = Null; + constinit KVirtualAddress g_clkrst_address = Null; + + constinit ChannelData g_channel_area[ChannelCount] = {}; + + ALWAYS_INLINE u32 Read(KVirtualAddress address) { + return *GetPointer(address); + } + + ALWAYS_INLINE void Write(KVirtualAddress address, u32 value) { + *GetPointer(address) = value; + } + + void InitializeDeviceVirtualAddresses() { + /* Retrieve randomized mappings. */ + g_evp_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsExceptionVectors); + g_flow_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsFlowController); + g_prictlr_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsPrimaryICtlr); + g_sema_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsSemaphore); + g_atomics_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsAtomics); + g_clkrst_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_LegacyLpsClkRst); + } + + /* NOTE: linux "do_cc4_init" */ + void ConfigureCc3AndCc4() { + /* Configure CC4/CC3 as enabled with time threshold as 2 microseconds. */ + Write(g_flow_address + FLOW_CTLR_CC4_HVC_CONTROL, (0x2 << 3) | 0x1); + + /* Configure Retention with threshold 2 microseconds. */ + Write(g_flow_address + FLOW_CTLR_CC4_RETENTION_CONTROL, (0x2 << 3)); + + /* Configure CC3/CC3 retry threshold as 2 microseconds. */ + Write(g_flow_address + FLOW_CTLR_CC4_HVC_RETRY, (0x2 << 3)); + + /* Read the retry register to ensure writes take. */ + Read(g_flow_address + FLOW_CTLR_CC4_HVC_RETRY); + } + + constexpr bool IsValidMessageDataSize(int size) { + return 0 <= size && size < MessageDataSizeMax; + } + + /* NOTE: linux "bpmp_valid_txfer" */ + constexpr bool IsTransferValid(const void *ob, int ob_size, void *ib, int ib_size) { + return IsValidMessageDataSize(ob_size) && IsValidMessageDataSize(ib_size) && (ob_size == 0 || ob != nullptr) && (ib_size == 0 || ib != nullptr); + } + + /* NOTE: linux "bpmp_ob_channel" */ + int BpmpGetOutboundChannel() { + return GetCurrentCoreId(); + } + + /* NOTE: linux "bpmp_ch_sta" */ + u32 BpmpGetChannelState(int channel) { + cpu::DataSynchronizationBarrier(); + return Read(g_sema_address + RES_SEMA_SHRD_SMP_STA) & CH_MASK(channel); + } + + /* NOTE: linux "bpmp_master_free" */ + bool BpmpIsMasterFree(int channel) { + return BpmpGetChannelState(channel) == MA_FREE(channel); + } + + /* NOTE: linux "bpmp_master_acked" */ + bool BpmpIsMasterAcked(int channel) { + return BpmpGetChannelState(channel) == MA_ACKD(channel); + } + + /* NOTE: linux "bpmp_signal_slave" */ + void BpmpSignalSlave(int channel) { + Write(g_sema_address + RES_SEMA_SHRD_SMP_CLR, CH_MASK(channel)); + cpu::DataSynchronizationBarrier(); + } + + /* NOTE: linux "bpmp_free_master" */ + void BpmpFreeMaster(int channel) { + /* Transition state from ack'd to free. */ + Write(g_sema_address + RES_SEMA_SHRD_SMP_CLR, ((MA_ACKD(channel)) ^ (MA_FREE(channel)))); + cpu::DataSynchronizationBarrier(); + } + + /* NOTE: linux "bpmp_ring_doorbell" */ + void BpmpRingDoorbell() { + Write(g_prictlr_address + ICTLR_FIR_SET(INT_SHR_SEM_OUTBOX_IBF), FIR_BIT(INT_SHR_SEM_OUTBOX_IBF)); + cpu::DataSynchronizationBarrier(); + } + + /* NOTE: linux "bpmp_wait_master_free" */ + int BpmpWaitMasterFree(int channel) { + /* Check if the master is already freed. */ + if (BpmpIsMasterFree(channel)) { + return 0; + } + + /* Spin-poll for the master to be freed until timeout occurs. */ + const auto start_tick = KHardwareTimer::GetTick(); + const auto timeout = ams::svc::Tick(ChannelTimeout); + do { + if (BpmpIsMasterFree(channel)) { + return 0; + } + } while ((KHardwareTimer::GetTick() - start_tick) < timeout); + + /* The master didn't become free. */ + return -1; + } + + /* NOTE: linux "bpmp_wait_ack" */ + int BpmpWaitAck(int channel) { + /* Check if the master is already ACK'd. */ + if (BpmpIsMasterAcked(channel)) { + return 0; + } + + /* Spin-poll for the master to be ACK'd until timeout occurs. */ + const auto start_tick = KHardwareTimer::GetTick(); + const auto timeout = ams::svc::Tick(ChannelTimeout); + do { + if (BpmpIsMasterAcked(channel)) { + return 0; + } + } while ((KHardwareTimer::GetTick() - start_tick) < timeout); + + /* The master didn't get ACK'd. */ + return -1; + } + + /* NOTE: linux "bpmp_write_ch" */ + int BpmpWriteChannel(int channel, int mrq, int flags, const void *data, size_t data_size) { + /* Wait to be able to master the mailbox. */ + if (int res = BpmpWaitMasterFree(channel); res != 0) { + return res; + } + + /* Prepare the message. */ + MailboxData *mb = g_channel_area[channel].ob; + mb->code = mrq; + mb->flags = flags; + if (data != nullptr) { + std::memcpy(mb->data, data, data_size); + } + + /* Signal to slave that message is available. */ + BpmpSignalSlave(channel); + + return 0; + } + + /* NOTE: linux "__bpmp_read_ch" */ + int BpmpReadChannel(int channel, void *data, size_t data_size) { + /* Get the message. */ + MailboxData *mb = g_channel_area[channel].ib; + + /* Copy any return data. */ + if (data != nullptr) { + std::memcpy(data, mb->data, data_size); + } + + /* Free the channel. */ + BpmpFreeMaster(channel); + + /* Return result. */ + return mb->code; + } + + /* NOTE: linux "tegra_bpmp_send_receive_atomic" or "tegra_bpmp_send_receive". */ + int BpmpSendAndReceive(int mrq, const void *ob, int ob_size, void *ib, int ib_size) { + /* Validate that the data transfer is valid. */ + if (!IsTransferValid(ob, ob_size, ib, ib_size)) { + return -1; + } + + /* Validate that the bpmp is connected. */ + if (!g_bpmp_connected) { + return -1; + } + + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Acquire exclusive access to send mrqs. */ + KScopedSpinLock lk(g_bpmp_mrq_lock); + + /* Send the message. */ + int channel = BpmpGetOutboundChannel(); + if (int res = BpmpWriteChannel(channel, mrq, BPMP_MSG_DO_ACK, ob, ob_size); res != 0) { + return res; + } + + /* Send "doorbell" irq to the bpmp firmware. */ + BpmpRingDoorbell(); + + /* Wait for the bpmp firmware to acknowledge our request. */ + if (int res = BpmpWaitAck(channel); res != 0) { + return res; + } + + /* Read the data the bpmp sent back. */ + return BpmpReadChannel(channel, ib, ib_size); + } + + /* NOTE: linux "tegra_bpmp_send" */ + int BpmpSend(int mrq, const void *ob, int ob_size) { + /* Validate that the data transfer is valid. */ + if (!IsTransferValid(ob, ob_size, nullptr, 0)) { + return -1; + } + + /* Validate that the bpmp is connected. */ + if (!g_bpmp_connected) { + return -1; + } + + /* Disable interrupts. */ + KScopedInterruptDisable di; + + /* Acquire exclusive access to send mrqs. */ + KScopedSpinLock lk(g_bpmp_mrq_lock); + + /* Send the message. */ + int channel = BpmpGetOutboundChannel(); + if (int res = BpmpWriteChannel(channel, mrq, 0, ob, ob_size); res != 0) { + return res; + } + + /* Send "doorbell" irq to the bpmp firmware. */ + BpmpRingDoorbell(); + + return 0; + } + + /* NOTE: modified linux "tegra_bpmp_enable_suspend" */ + int BpmpEnableSuspend(int mode, int flags) { + /* Prepare data for bpmp. */ + const s32 data[] = { mode, flags }; + + /* Send the data. */ + return BpmpSend(MRQ_ENABLE_SUSPEND, data, sizeof(data)); + } + + /* NOTE: linux "__bpmp_connect" */ + int ConnectToBpmp() { + /* Check if we've already connected. */ + if (g_bpmp_connected) { + return 0; + } + + /* Verify that the resource semaphore state is set. */ + if (Read(g_sema_address + RES_SEMA_SHRD_SMP_STA) == 0) { + return -1; + } + + /* Get the channels, which the bpmp firmware has configured in advance. */ + { + const KVirtualAddress iram_virt_addr = KMemoryLayout::GetDeviceVirtualAddress (KMemoryRegionType_LegacyLpsIram); + const KPhysicalAddress iram_phys_addr = KMemoryLayout::GetDevicePhysicalAddress(KMemoryRegionType_LegacyLpsIram); + + for (auto i = 0; i < ChannelCount; ++i) { + /* Trigger a get command for the desired channel. */ + Write(g_atomics_address + ATOMICS_AP0_TRIGGER, TRIGGER_CMD_GET | (i << 16)); + + /* Retrieve the channel phys-addr-in-iram, and convert it to a kernel address. */ + auto *ch = GetPointer(iram_virt_addr + (Read(g_atomics_address + ATOMICS_AP0_RESULT(i)) - GetInteger(iram_phys_addr))); + + /* Verify the channel isn't null. */ + /* NOTE: This is an utterly nonsense check, as this would require the bpmp firmware to specify */ + /* a phys-to-virt diff as an address. On 1.0.0, which had no ASLR, this was 0x8028C000. */ + /* However, Nintendo has the check, and we'll preserve it to be faithful. */ + if (ch == nullptr) { + return -1; + } + + /* Set the channel in the channel area. */ + g_channel_area[i].ib = ch; + g_channel_area[i].ob = ch; + } + } + + /* Mark driver as connected to bpmp. */ + g_bpmp_connected = true; + + return 0; + } + + /* NOTE: Modified linux "bpmp_mail_init" */ + int InitializeBpmpMail() { + /* Check if we've already initialized. */ + if (g_bpmp_mail_initialized) { + return 0; + } + + /* Mark function as having been called. */ + g_bpmp_mail_initialized = true; + + /* Forward declare result/reply variables. */ + int res, request = 0, reply = 0; + + /* Try to connect to the bpmp. */ + if (res = ConnectToBpmp(); res != 0) { + MESOSPHERE_LOG("bpmp: connect error returns %d\n", res); + return res; + } + + /* Ensure that we can successfully ping the bpmp. */ + request = 1; + if (res = BpmpSendAndReceive(MRQ_PING, std::addressof(request), sizeof(request), std::addressof(reply), sizeof(reply)); res != 0) { + MESOSPHERE_LOG("bpmp: MRQ_PING error returns %d with reply %d\n", res, reply); + return res; + } + + /* Configure the PMIC. */ + request = 1; + if (res = BpmpSendAndReceive(MRQ_CPU_PMIC_SELECT, std::addressof(request), sizeof(request), std::addressof(reply), sizeof(reply)); res != 0) { + MESOSPHERE_LOG("bpmp: MRQ_CPU_PMIC_SELECT for MAX77621 error returns %d with reply %d\n", res, reply); + return res; + } + + return 0; + } } void Initialize() { if (!g_lps_init_done) { - MESOSPHERE_UNIMPLEMENTED(); + /* Get the addresses of the devices the driver needs. */ + InitializeDeviceVirtualAddresses(); + + /* Configure CC3/CC4. */ + ConfigureCc3AndCc4(); + + /* Initialize ccplex <-> bpmp mail. */ + /* NOTE: Nintendo does not check that this call succeeds. */ + InitializeBpmpMail(); g_lps_init_done = true; } } - Result EnableSuspend() { - MESOSPHERE_UNIMPLEMENTED(); + Result EnableSuspend(bool enable) { + /* If we're not on core 0, there's nothing to do. */ + R_SUCCEED_IF(GetCurrentCoreId() != 0); + + /* If we're not enabling suspend, there's nothing to do. */ + R_SUCCEED_IF(!enable); + + /* Instruct BPMP to enable suspend-to-sc7. */ + R_UNLESS(BpmpEnableSuspend(TEGRA_BPMP_PM_SC7, 0) == 0, svc::ResultInvalidState()); + + return ResultSuccess(); } void InvokeCpuSleepHandler(uintptr_t arg, uintptr_t entry) { @@ -45,6 +400,11 @@ namespace ams::kern::board::nintendo::nx::lps { /* Invoke the sleep hander. */ KSleepManager::CpuSleepHandler(arg, entry); + + /* TODO: restore saved clkrst reg */ + + /* Configure CC3/CC4. */ + ConfigureCc3AndCc4(); } } \ No newline at end of file diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.hpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.hpp index e63e409b7..49ff4b3d3 100644 --- a/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.hpp +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_lps_driver.hpp @@ -21,7 +21,7 @@ namespace ams::kern::board::nintendo::nx { namespace lps { void Initialize(); - Result EnableSuspend(); + Result EnableSuspend(bool enable); void InvokeCpuSleepHandler(uintptr_t arg, uintptr_t entry); } diff --git a/libraries/libmesosphere/source/board/nintendo/nx/kern_sema_registers.hpp b/libraries/libmesosphere/source/board/nintendo/nx/kern_sema_registers.hpp new file mode 100644 index 000000000..b623df176 --- /dev/null +++ b/libraries/libmesosphere/source/board/nintendo/nx/kern_sema_registers.hpp @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 this program. If not, see . + */ +#pragma once + +#define RES_SEMA_SHRD_SMP_STA 0x000 +#define RES_SEMA_SHRD_SMP_SET 0x004 +#define RES_SEMA_SHRD_SMP_CLR 0x008