/* * Copyright (c) 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 . */ #include #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_sema_registers.hpp" namespace ams::kern::board::nintendo::nx::lps { namespace { constexpr inline int ChannelCount = 12; constexpr inline TimeSpan ChannelTimeout = TimeSpan::FromSeconds(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 KVirtualAddress g_pmc_address = Null; constinit ChannelData g_channel_area[ChannelCount] = {}; constinit u32 g_csite_clk_source = 0; 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); g_pmc_address = KMemoryLayout::GetDeviceVirtualAddress(KMemoryRegionType_PowerManagementController); } /* 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) { /* 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(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()); R_SUCCEED(); } void InvokeCpuSleepHandler(uintptr_t arg, uintptr_t entry) { /* Verify that we're allowed to perform suspension. */ MESOSPHERE_ABORT_UNLESS(g_lps_init_done); MESOSPHERE_ABORT_UNLESS(GetCurrentCoreId() == 0); /* Save the CSITE clock source. */ g_csite_clk_source = Read(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE); /* Configure CSITE clock source as CLK_M. */ Write(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE, (0x6 << 29)); /* Clear the top bit of PMC_SCRATCH4. */ Write(g_pmc_address + APBDEV_PMC_SCRATCH4, Read(g_pmc_address + APBDEV_PMC_SCRATCH4) & 0x7FFFFFFF); /* Write 1 to PMC_SCRATCH0. This will cause the bootrom to use the warmboot code-path. */ Write(g_pmc_address + APBDEV_PMC_SCRATCH0, 1); /* Read PMC_SCRATCH0 to be sure our write takes. */ Read(g_pmc_address + APBDEV_PMC_SCRATCH0); /* Invoke the sleep hander. */ KSleepManager::CpuSleepHandler(arg, entry); /* Disable deep power down. */ Write(g_pmc_address + APBDEV_PMC_DPD_ENABLE, 0); /* Restore the saved CSITE clock source. */ Write(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE, g_csite_clk_source); /* Read the CSITE clock source to ensure our configuration takes. */ Read(g_clkrst_address + CLK_RST_CONTROLLER_CLK_SOURCE_CSITE); /* Configure CC3/CC4. */ ConfigureCc3AndCc4(); } void ResumeBpmpFirmware() { /* Halt the bpmp. */ Write(g_flow_address + FLOW_CTLR_HALT_COP_EVENTS, (0x2 << 29)); /* Hold the bpmp in reset. */ Write(g_clkrst_address + CLK_RST_CONTROLLER_RST_DEV_L_SET, 0x2); /* Read the saved bpmp entrypoint, and write it to the relevant exception vector. */ const u32 bpmp_entry = Read(g_pmc_address + APBDEV_PMC_SCRATCH39); Write(g_evp_address + EVP_COP_RESET_VECTOR, bpmp_entry); /* Verify that we can read back the address we wrote. */ while (Read(g_evp_address + EVP_COP_RESET_VECTOR) != bpmp_entry) { /* ... */ } /* Spin for 40 ticks, to give enough time for the bpmp to be reset. */ const auto start_tick = KHardwareTimer::GetTick(); do { __asm__ __volatile__("" ::: "memory"); } while ((KHardwareTimer::GetTick() - start_tick) < 40); /* Take the bpmp out of reset. */ Write(g_clkrst_address + CLK_RST_CONTROLLER_RST_DEV_L_CLR, 0x2); /* Resume the bpmp. */ Write(g_flow_address + FLOW_CTLR_HALT_COP_EVENTS, (0x0 << 29)); } }