Split CFG11 and PDN into separate headers. Also (nearly) completed them.
Reimplemented filesystem access for ARM11. Moved ROM loading to ARM11.
This commit is contained in:
parent
4316ad5c85
commit
aecbee03e7
|
@ -22,13 +22,59 @@
|
|||
|
||||
|
||||
#define CFG11_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x40000)
|
||||
#define REG_CFG11_FIQ_CNT *((vu8* )(CFG11_REGS_BASE + 0x0104))
|
||||
#define REG_CFG11_SPI_CNT *((vu16*)(CFG11_REGS_BASE + 0x01C0))
|
||||
#define REG_UNK_10140400 *((vu8* )(CFG11_REGS_BASE + 0x0400))
|
||||
#define REG_UNK_10140410 *((vu32*)(CFG11_REGS_BASE + 0x0410))
|
||||
#define REG_CFG11_BOOTROM_OVERLAY_CNT *((vu8* )(CFG11_REGS_BASE + 0x0420))
|
||||
#define REG_CFG11_BOOTROM_OVERLAY_VAL *((vu32*)(CFG11_REGS_BASE + 0x0424))
|
||||
#define REG_CFG11_SOCINFO *((vu16*)(CFG11_REGS_BASE + 0x0FFC))
|
||||
#define REG_CFG11_MPCORE_CLKCNT *((vu16*)(CFG11_REGS_BASE + 0x1300))
|
||||
#define REG_CFG11_MPCORE_CNT *((vu16*)(CFG11_REGS_BASE + 0x1304))
|
||||
#define REGs_CFG11_MPCORE_BOOTCNT ((vu8* )(CFG11_REGS_BASE + 0x1310))
|
||||
#define REG_CFG11_FIQ_MASK *(( vu8*)(CFG11_REGS_BASE + 0x104))
|
||||
#define REG_CFG11_UNK105 *(( vu8*)(CFG11_REGS_BASE + 0x105)) // Debug related? Mask?
|
||||
#define REG_CFG11_UNK108 *(( vu8*)(CFG11_REGS_BASE + 0x108)) // LGY gamecard related?
|
||||
#define REG_CFG11_CDMA_CNT *(( vu8*)(CFG11_REGS_BASE + 0x10C))
|
||||
#define REG_CFG11_UNK110 *(( vu8*)(CFG11_REGS_BASE + 0x110)) // VRAM related?
|
||||
#define REG_CFG11_GPUPROT *(( vu16*)(CFG11_REGS_BASE + 0x140))
|
||||
#define REG_CFG11_WIFI_POWER *(( vu8*)(CFG11_REGS_BASE + 0x180)) // Used for flight mode?
|
||||
#define REG_CFG11_SPI_CNT *(( vu16*)(CFG11_REGS_BASE + 0x1C0))
|
||||
#define REG_CFG11_UNK200 *(( vu32*)(CFG11_REGS_BASE + 0x200)) // GPIO3 related? 8x4 bits.
|
||||
#define REG_CFG11_GPU_N3DS_CNT *(( vu8*)(CFG11_REGS_BASE + 0x400)) // New3DS-only.
|
||||
#define REG_CFG11_CDMA_PERIPHERALS *(( vu32*)(CFG11_REGS_BASE + 0x410)) // New3DS-only.
|
||||
#define REG_CFG11_BOOTROM_OVERLAY_CNT *(( vu8*)(CFG11_REGS_BASE + 0x420)) // New3DS-only.
|
||||
#define REG_CFG11_BOOTROM_OVERLAY_VAL *(( vu32*)(CFG11_REGS_BASE + 0x424)) // New3DS-only.
|
||||
#define REG_CFG11_UNK428 *(( vu8*)(CFG11_REGS_BASE + 0x428)) // New3DS-only. 1 bit. Enable CPU core 1 access to overlay regs?
|
||||
#define REG_CFG11_SOCINFO *((const vu16*)(CFG11_REGS_BASE + 0xFFC))
|
||||
|
||||
|
||||
// REG_CFG11_FIQ_MASK
|
||||
#define FIQ_MASK_CPU0 (1u)
|
||||
#define FIQ_MASK_CPU1 (1u<<1)
|
||||
#define FIQ_MASK_CPU2 (1u<<2) // New3DS-only.
|
||||
#define FIQ_MASK_CPU3 (1u<<3) // New3DS-only.
|
||||
|
||||
// REG_CFG11_CDMA_CNT
|
||||
#define CDMA_CNT_MIC_E (1u)
|
||||
#define CDMA_CNT_NTRCARD_E (1u<<1)
|
||||
#define CDMA_CNT_CAM1_E (1u<<2)
|
||||
#define CDMA_CNT_CAM2_E (1u<<3)
|
||||
#define CDMA_CNT_SDIO2_E (1u<<4) // WiFi
|
||||
#define CDMA_CNT_SDIO3_E (1u<<5)
|
||||
|
||||
// REG_CFG11_GPUPROT
|
||||
// TODO
|
||||
|
||||
// REG_CFG11_WIFI_POWER
|
||||
#define WIFI_POWER_ON (1u)
|
||||
|
||||
// REG_CFG11_SPI_CNT
|
||||
#define SPI_CNT_SPI1_NEW_IF (1u) // New interface (NSPI).
|
||||
#define SPI_CNT_SPI2_NEW_IF (1u<<1)
|
||||
#define SPI_CNT_SPI3_NEW_IF (1u<<2)
|
||||
|
||||
// REG_CFG11_GPU_N3DS_CNT
|
||||
#define GPU_N3DS_CNT_N3DS_MODE (1u) // Enable access to mem extensions.
|
||||
#define GPU_N3DS_CNT_TEX_FIX (1u<<1) // Fixes some texture glitches in New3DS mode.
|
||||
|
||||
// REG_CFG11_CDMA_PERIPHERALS
|
||||
#define CDMA_PERIPHERALS_ALL (0x3FFFFu)
|
||||
|
||||
// REG_CFG11_BOOTROM_OVERLAY_CNT
|
||||
#define BOOTROM_OVERLAY_CNT_E (1u)
|
||||
|
||||
// REG_CFG11_SOCINFO
|
||||
#define SOCINFO_O3DS (1u) // Also set on New3DS.
|
||||
#define SOCINFO_N3DS_PROTO (1u<<1) // Never saw the daylight?
|
||||
#define SOCINFO_N3DS (1u<<2) // Set on New3DS.
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
#pragma once
|
||||
|
||||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2020 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
#include "mem_map.h"
|
||||
|
||||
|
||||
#define PDN_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x41000)
|
||||
#define REG_PDN_CNT *((vu16*)(PDN_REGS_BASE + 0x000))
|
||||
#define REG_PDN_WAKE_ENABLE *((vu32*)(PDN_REGS_BASE + 0x008))
|
||||
#define REG_PDN_WAKE_REASON *((vu32*)(PDN_REGS_BASE + 0x00C)) // Write 1 to acknowledge and 0 to clear?
|
||||
#define REG_PDN_GPU_CNT *((vu32*)(PDN_REGS_BASE + 0x200))
|
||||
#define REG_PDN_VRAM_CNT *((vu8* )(PDN_REGS_BASE + 0x204)) // This reg doesn't seem to exist on retail hardware.
|
||||
#define REG_PDN_LCD_CNT *((vu8* )(PDN_REGS_BASE + 0x208)) // This reg doesn't seem to exist on retail hardware.
|
||||
#define REG_PDN_FCRAM_CNT *((vu8* )(PDN_REGS_BASE + 0x210))
|
||||
#define REG_PDN_I2S_CNT *((vu8* )(PDN_REGS_BASE + 0x220))
|
||||
#define REG_PDN_CAM_CNT *((vu8* )(PDN_REGS_BASE + 0x224))
|
||||
#define REG_PDN_DSP_CNT *((vu8* )(PDN_REGS_BASE + 0x230))
|
||||
#define REG_PDN_G1_CNT *((vu8* )(PDN_REGS_BASE + 0x240)) // Hantro G1 decoder.
|
||||
#define REG_PDN_MPCORE_SOCMODE *((vu16*)(PDN_REGS_BASE + 0x300))
|
||||
#define REG_PDN_MPCORE_CNT *((vu16*)(PDN_REGS_BASE + 0x304)) // Is this reg actually only vu8?
|
||||
#define REGs_PDN_MPCORE_BOOTCNT ((vu8* )(PDN_REGS_BASE + 0x310))
|
||||
|
||||
|
||||
// REG_PDN_CNT
|
||||
#define PDN_CNT_SLEEP (1u) // Set this bit to enter sleep mode.
|
||||
#define PDN_CNT_VRAM_OFF (1u<<15) // Set when VRAM is powered off.
|
||||
|
||||
// REG_PDN_WAKE_ENABLE and REG_PDN_WAKE_REASON
|
||||
enum
|
||||
{
|
||||
PDN_WAKE_PADCNT = 1u,
|
||||
PDN_WAKE_SHELL_OPENED = 1u<<3,
|
||||
PDN_WAKE_HEADPH_NOT_PLUGGED_IN = 1u<<4, // Really?
|
||||
PDN_WAKE_UNK6 = 1u<<6, // DSi mode related.
|
||||
PDN_WAKE_SDIO1 = 1u<<7,
|
||||
PDN_WAKE_SDIO2 = 1u<<8,
|
||||
PDN_WAKE_SDIO3 = 1u<<16,
|
||||
// 17-28 maybe GPIO3 0-11?
|
||||
PDN_WAKE_GAMECARD_INSERT = 1u<<29, // ?
|
||||
PDN_WAKE_TOUCHPEN_DOWN = 1u<<30,
|
||||
PDN_WAKE_UNK31 = 1u<<31 // Also shell related?
|
||||
};
|
||||
|
||||
// REG_PDN_GPU_CNT
|
||||
// Note: The resets are active low.
|
||||
enum
|
||||
{
|
||||
PDN_GPU_CNT_RST_REGS = 1u, // And more?
|
||||
PDN_GPU_CNT_RST_PSC = 1u<<1, // ?
|
||||
PDN_GPU_CNT_RST_GEOSHADER = 1u<<2, // ?
|
||||
PDN_GPU_CNT_RST_RASTERIZER = 1u<<3, // ?
|
||||
PDN_GPU_CNT_RST_PPF = 1u<<4,
|
||||
PDN_GPU_CNT_RST_PDC = 1u<<5, // ?
|
||||
PDN_GPU_CNT_RST_PDC2 = 1u<<6, // Maybe pixel pipeline or so?
|
||||
|
||||
PDN_GPU_CNT_RST_ALL = (PDN_GPU_CNT_RST_PDC2<<1) - 1
|
||||
};
|
||||
|
||||
#define PDN_GPU_CNT_CLK_E (1u<<16)
|
||||
|
||||
// REG_PDN_VRAM_CNT
|
||||
#define PDN_VRAM_CNT_CLK_E (1u)
|
||||
|
||||
// REG_PDN_LCD_CNT
|
||||
#define PDN_LCD_CNT_PWR_MGR_OFF (1u) // Power management off?
|
||||
|
||||
// REG_PDN_FCRAM_CNT
|
||||
// Note: Reset is active low.
|
||||
#define PDN_FCRAM_CNT_RST (1u)
|
||||
#define PDN_FCRAM_CNT_CLK_E (1u<<1)
|
||||
#define PDN_FCRAM_CNT_CLK_E_ACK (1u<<2) // Gets set or unset depending on CLK_E.
|
||||
|
||||
// REG_PDN_I2S_CNT
|
||||
#define PDN_I2S_CNT_I2S_CLK1_E (1u) // ? Unused?
|
||||
#define PDN_I2S_CNT_I2S_CLK2_E (1u<<1)
|
||||
|
||||
// REG_PDN_CAM_CNT
|
||||
#define PDN_CAM_CNT_CLK_E (1u)
|
||||
|
||||
// REG_PDN_DSP_CNT
|
||||
// Note: Reset is active low.
|
||||
#define PDN_DSP_CNT_RST (1u)
|
||||
#define PDN_DSP_CNT_CLK_E (1u<<1)
|
||||
|
||||
// REG_PDN_G1_CNT
|
||||
// TODO: Active low or high?
|
||||
#define PDN_G1_CNT_RST (1u)
|
||||
|
||||
// REG_PDN_MPCORE_SOCMODE
|
||||
typedef enum
|
||||
{
|
||||
SOCMODE_O3DS_268MHz = 0u,
|
||||
SOCMODE_N3DS_268MHz = 1u, // Also enables FCRAM extension.
|
||||
SOCMODE_N3DS_PROTO_268MHz = 2u, // Also enables FCRAM extension?
|
||||
SOCMODE_N3DS_PROTO_536MHz = 3u, // Also enables FCRAM extension?
|
||||
SOCMODE_N3DS_804MHz = 5u, // Also enables FCRAM extension.
|
||||
|
||||
SOCMODE_MASK = 7u
|
||||
} PdnSocmode;
|
||||
|
||||
#define PDN_MPCORE_SOCMODE_ACK (1u<<15)
|
||||
|
||||
// REG_PDN_MPCORE_CNT
|
||||
#define PDN_MPCORE_CNT_MEM_EXT_E (1u) // Does it actually affect all mem extensions or just QTM?
|
||||
#define PDN_MPCORE_CNT_L2C_E (1u<<8)
|
||||
|
||||
// REGs_PDN_MPCORE_BOOTCNT
|
||||
// Note: Reset is active low.
|
||||
#define MPCORE_BOOTCNT_RST (1u) // Core 2/3 only. Reset and instruction overlay enable.
|
||||
#define MPCORE_BOOTCNT_D_OVERL_E (1u<<1) // Core 2/3 only. Data overlay enable. Also used to signal a core booted.
|
||||
#define MPCORE_BOOTCNT_RST_STAT (1u<<4)
|
||||
#define MPCORE_BOOTCNT_UNK (1u<<5)
|
||||
|
||||
|
||||
|
||||
void PDN_core123Init(void);
|
||||
void PDN_setSocmode(PdnSocmode socmode);
|
||||
void PDN_poweroffCore23(void);
|
|
@ -12,16 +12,35 @@ typedef u32 Result;
|
|||
enum
|
||||
{
|
||||
// Common errors.
|
||||
RES_OK = 0u,
|
||||
RES_SD_CARD_REMOVED = 1u,
|
||||
RES_MOUNT_ERR = 2u,
|
||||
RES_FILE_OPEN_ERR = 3u,
|
||||
RES_FILE_READ_ERR = 4u,
|
||||
RES_FILE_WRITE_ERR = 5u,
|
||||
RES_OK = 0u,
|
||||
RES_SD_CARD_REMOVED = 1u,
|
||||
RES_INVALID_ARG = 2u,
|
||||
|
||||
// fatfs errors.
|
||||
// Caution: Update fs.c on ARM9 if this changes!
|
||||
RES_FR_DISK_ERR = 3u, /* (1) A hard error occurred in the low level disk I/O layer */
|
||||
RES_FR_INT_ERR = 4u, /* (2) Assertion failed */
|
||||
RES_FR_NOT_READY = 5u, /* (3) The physical drive cannot work */
|
||||
RES_FR_NO_FILE = 6u, /* (4) Could not find the file */
|
||||
RES_FR_NO_PATH = 7u, /* (5) Could not find the path */
|
||||
RES_FR_INVALID_NAME = 8u, /* (6) The path name format is invalid */
|
||||
RES_FR_DENIED = 9u, /* (7) Access denied due to prohibited access or directory full */
|
||||
RES_FR_EXIST = 10u, /* (8) Access denied due to prohibited access */
|
||||
RES_FR_INVALID_OBJECT = 11u, /* (9) The file/directory object is invalid */
|
||||
RES_FR_WRITE_PROTECTED = 12u, /* (10) The physical drive is write protected */
|
||||
RES_FR_INVALID_DRIVE = 13u, /* (11) The logical drive number is invalid */
|
||||
RES_FR_NOT_ENABLED = 14u, /* (12) The volume has no work area */
|
||||
RES_FR_NO_FILESYSTEM = 15u, /* (13) There is no valid FAT volume */
|
||||
RES_FR_MKFS_ABORTED = 16u, /* (14) The f_mkfs() aborted due to any problem */
|
||||
RES_FR_TIMEOUT = 17u, /* (15) Could not get a grant to access the volume within defined period */
|
||||
RES_FR_LOCKED = 18u, /* (16) The operation is rejected according to the file sharing policy */
|
||||
RES_FR_NOT_ENOUGH_CORE = 19u, /* (17) LFN working buffer could not be allocated */
|
||||
RES_FR_TOO_MANY_OPEN_FILES = 20u, /* (18) Number of open files > FF_FS_LOCK */
|
||||
RES_FR_INVALID_PARAMETER = 21u, /* (19) Given parameter is invalid */
|
||||
|
||||
// Custom errors.
|
||||
RES_ROM_TOO_BIG = MAKE_CUSTOM_ERR(0),
|
||||
RES_GBA_RTC_ERR = MAKE_CUSTOM_ERR(1)
|
||||
RES_ROM_TOO_BIG = MAKE_CUSTOM_ERR(0),
|
||||
RES_GBA_RTC_ERR = MAKE_CUSTOM_ERR(1)
|
||||
};
|
||||
|
||||
#undef MAKE_CUSTOM_ERR
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
#pragma once
|
||||
|
||||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2017 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
#include "error_codes.h"
|
||||
#ifdef ARM11
|
||||
#include "../thirdparty/fatfs/ff.h"
|
||||
#else
|
||||
#include "fatfs/ff.h"
|
||||
#endif // ifdef ARM11
|
||||
|
||||
|
||||
#define FS_MAX_DRIVES (FF_VOLUMES)
|
||||
#define FS_DRIVE_NAMES "sdmc:/"
|
||||
#define FS_MAX_FILES (1u)
|
||||
#define FS_MAX_DIRS (1u)
|
||||
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FS_DRIVE_SDMC = 0u
|
||||
} FsDrive;
|
||||
|
||||
typedef u32 FHandle;
|
||||
typedef u32 DHandle;
|
||||
|
||||
|
||||
|
||||
Result fMount(FsDrive drive);
|
||||
Result fUnmount(FsDrive drive);
|
||||
Result fGetFree(FsDrive drive, u64 *const size);
|
||||
Result fOpen(FHandle *const hOut, const char *const path, u8 mode);
|
||||
Result fRead(FHandle h, void *const buf, u32 size, u32 *const bytesRead);
|
||||
Result fWrite(FHandle h, const void *const buf, u32 size, u32 *const bytesWritten);
|
||||
Result fSync(FHandle h);
|
||||
Result fLseek(FHandle h, u32 off);
|
||||
u32 fTell(FHandle h);
|
||||
u32 fSize(FHandle h);
|
||||
Result fClose(FHandle h);
|
||||
Result fStat(const char *const path, FILINFO *const fi);
|
||||
Result fOpenDir(DHandle *const hOut, const char *const path);
|
||||
Result fReadDir(DHandle h, FILINFO *const fi, u32 num, u32 *const entriesRead);
|
||||
Result fCloseDir(DHandle h);
|
||||
Result fMkdir(const char *const path);
|
||||
Result fRename(const char *const old, const char *const _new);
|
||||
Result fUnlink(const char *const path);
|
||||
|
||||
#ifdef ARM9
|
||||
void fsDeinit(void);
|
||||
#endif // ifdef ARM9
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2017 derrek, profi200
|
||||
* Copyright (C) 2020 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -19,8 +19,8 @@
|
|||
*/
|
||||
|
||||
#include "types.h"
|
||||
#include "error_codes.h"
|
||||
|
||||
|
||||
|
||||
void CPU_setClock(u16 clk);
|
||||
void CPU_poweroffCore23(void);
|
||||
Result fsQuickRead(const char *const path, u32 off, void *buf, u32 size);
|
|
@ -4,9 +4,18 @@
|
|||
#include "error_codes.h"
|
||||
|
||||
|
||||
#define MAX_ROM_SIZE (1024u * 1024 * 32)
|
||||
#define MAX_SAVE_SIZE (1024u * 128)
|
||||
#define ARM7_STUB_LOC (0x3007E00u)
|
||||
#define ARM7_STUB_LOC9 (0x80BFE00u)
|
||||
#define ROM_LOC (0x20000000u)
|
||||
#define SAVE_LOC (0x8080000u)
|
||||
|
||||
|
||||
// REG_LGY_MODE
|
||||
#define LGY_MODE_TWL (1u)
|
||||
#define LGY_MODE_AGB (2u)
|
||||
#define LGY_MODE_TWL (1u)
|
||||
#define LGY_MODE_AGB (2u)
|
||||
#define LGY_MODE_START (1u<<15)
|
||||
|
||||
// REG_LGY_GBA_SAVE_TYPE
|
||||
enum
|
||||
|
@ -94,13 +103,14 @@ typedef struct
|
|||
|
||||
|
||||
|
||||
Result LGY_prepareGbaMode(bool gbaBios, u16 saveType);
|
||||
Result LGY_setGbaRtc(const GbaRtc rtc);
|
||||
Result LGY_getGbaRtc(GbaRtc *const out);
|
||||
Result LGY_backupGbaSave(void);
|
||||
#ifdef ARM11
|
||||
Result LGY_prepareGbaMode(bool gbaBios, u16 saveType, const char *const romPath, const char *const savePath);
|
||||
void LGY_switchMode(void);
|
||||
void LGY_handleEvents(void);
|
||||
void LGY_deinit(void);
|
||||
#elif ARM9
|
||||
Result LGY_backupGbaSave(void);
|
||||
Result LGY_prepareGbaMode(bool gbaBios, u16 saveType, const char *const savePath);
|
||||
#endif
|
||||
|
|
|
@ -37,15 +37,40 @@
|
|||
enum {_CMD9_C_BASE = __COUNTER__ + 1}; // Start at 0.
|
||||
typedef enum
|
||||
{
|
||||
IPC_CMD9_PREPARE_GBA = MAKE_CMD9(0, 0, 2),
|
||||
// Filesystem API.
|
||||
IPC_CMD9_FMOUNT = MAKE_CMD9(0, 0, 1),
|
||||
IPC_CMD9_FUNMOUNT = MAKE_CMD9(0, 0, 1),
|
||||
IPC_CMD9_FGETFREE = MAKE_CMD9(0, 1, 1),
|
||||
IPC_CMD9_FOPEN = MAKE_CMD9(1, 1, 1),
|
||||
IPC_CMD9_FREAD = MAKE_CMD9(0, 2, 1),
|
||||
IPC_CMD9_FWRITE = MAKE_CMD9(1, 1, 1),
|
||||
IPC_CMD9_FSYNC = MAKE_CMD9(0, 0, 1),
|
||||
IPC_CMD9_FLSEEK = MAKE_CMD9(0, 0, 2),
|
||||
IPC_CMD9_FTELL = MAKE_CMD9(0, 0, 1),
|
||||
IPC_CMD9_FSIZE = MAKE_CMD9(0, 0, 1),
|
||||
IPC_CMD9_FCLOSE = MAKE_CMD9(0, 0, 1),
|
||||
IPC_CMD9_FSTAT = MAKE_CMD9(1, 1, 0),
|
||||
IPC_CMD9_FOPEN_DIR = MAKE_CMD9(1, 1, 0),
|
||||
IPC_CMD9_FREAD_DIR = MAKE_CMD9(0, 2, 2),
|
||||
IPC_CMD9_FCLOSE_DIR = MAKE_CMD9(0, 0, 1),
|
||||
IPC_CMD9_FMKDIR = MAKE_CMD9(1, 0, 0),
|
||||
IPC_CMD9_FRENAME = MAKE_CMD9(2, 0, 0),
|
||||
IPC_CMD9_FUNLINK = MAKE_CMD9(1, 0, 0),
|
||||
|
||||
// open_agb_firm specific API.
|
||||
IPC_CMD9_PREPARE_GBA = MAKE_CMD9(1, 0, 2),
|
||||
IPC_CMD9_SET_GBA_RTC = MAKE_CMD9(0, 0, 2),
|
||||
IPC_CMD9_GET_GBA_RTC = MAKE_CMD9(0, 1, 0),
|
||||
IPC_CMD9_BACKUP_GBA_SAVE = MAKE_CMD9(0, 0, 0),
|
||||
|
||||
// Miscellaneous API.
|
||||
IPC_CMD9_PREPARE_POWER = MAKE_CMD9(0, 0, 0)
|
||||
} IpcCmd9;
|
||||
|
||||
enum {_CMD11_C_BASE = __COUNTER__ + 1}; // Start at 0.
|
||||
typedef enum
|
||||
{
|
||||
// Miscellaneous API.
|
||||
IPC_CMD11_PRINT_MSG = MAKE_CMD11(0, 0, 0), // Invalid on purpose. Will be decided later.
|
||||
IPC_CMD11_PANIC = MAKE_CMD11(0, 0, 0),
|
||||
IPC_CMD11_EXCEPTION = MAKE_CMD11(0, 0, 0)
|
||||
|
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2017 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "types.h"
|
||||
#include "error_codes.h"
|
||||
#include "fs.h"
|
||||
#include "ipc_handler.h"
|
||||
#include "hardware/pxi.h"
|
||||
|
||||
|
||||
|
||||
Result fMount(FsDrive drive)
|
||||
{
|
||||
const u32 cmdBuf = drive;
|
||||
return PXI_sendCmd(IPC_CMD9_FMOUNT, &cmdBuf, 1);
|
||||
}
|
||||
|
||||
Result fUnmount(FsDrive drive)
|
||||
{
|
||||
const u32 cmdBuf = drive;
|
||||
return PXI_sendCmd(IPC_CMD9_FUNMOUNT, &cmdBuf, 1);
|
||||
}
|
||||
|
||||
Result fGetFree(FsDrive drive, u64 *const size)
|
||||
{
|
||||
u32 cmdBuf[3];
|
||||
cmdBuf[0] = (u32)size;
|
||||
cmdBuf[1] = sizeof(u64);
|
||||
cmdBuf[2] = drive;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FGETFREE, cmdBuf, 3);
|
||||
}
|
||||
|
||||
Result fOpen(FHandle *const hOut, const char *const path, u8 mode)
|
||||
{
|
||||
u32 cmdBuf[5];
|
||||
cmdBuf[0] = (u32)path;
|
||||
cmdBuf[1] = strlen(path) + 1;
|
||||
cmdBuf[2] = (u32)hOut;
|
||||
cmdBuf[3] = sizeof(FHandle);
|
||||
cmdBuf[4] = mode;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FOPEN, cmdBuf, 5);
|
||||
}
|
||||
|
||||
Result fRead(FHandle h, void *const buf, u32 size, u32 *const bytesRead)
|
||||
{
|
||||
u32 cmdBuf[5];
|
||||
cmdBuf[0] = (u32)buf;
|
||||
cmdBuf[1] = size;
|
||||
cmdBuf[2] = (u32)bytesRead;
|
||||
cmdBuf[3] = sizeof(u32);
|
||||
cmdBuf[4] = h;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FREAD, cmdBuf, 5);
|
||||
}
|
||||
|
||||
Result fWrite(FHandle h, const void *const buf, u32 size, u32 *const bytesWritten)
|
||||
{
|
||||
u32 cmdBuf[5];
|
||||
cmdBuf[0] = (u32)buf;
|
||||
cmdBuf[1] = size;
|
||||
cmdBuf[2] = (u32)bytesWritten;
|
||||
cmdBuf[3] = sizeof(u32);
|
||||
cmdBuf[4] = h;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FWRITE, cmdBuf, 5);
|
||||
}
|
||||
|
||||
Result fSync(FHandle h)
|
||||
{
|
||||
const u32 cmdBuf = h;
|
||||
return PXI_sendCmd(IPC_CMD9_FSYNC, &cmdBuf, 1);
|
||||
}
|
||||
|
||||
Result fLseek(FHandle h, u32 off)
|
||||
{
|
||||
u32 cmdBuf[2];
|
||||
cmdBuf[0] = h;
|
||||
cmdBuf[1] = off;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FLSEEK, cmdBuf, 2);
|
||||
}
|
||||
|
||||
u32 fTell(FHandle h)
|
||||
{
|
||||
const u32 cmdBuf = h;
|
||||
return PXI_sendCmd(IPC_CMD9_FTELL, &cmdBuf, 1);
|
||||
}
|
||||
|
||||
u32 fSize(FHandle h)
|
||||
{
|
||||
const u32 cmdBuf = h;
|
||||
return PXI_sendCmd(IPC_CMD9_FSIZE, &cmdBuf, 1);
|
||||
}
|
||||
|
||||
Result fClose(FHandle h)
|
||||
{
|
||||
const u32 cmdBuf = h;
|
||||
return PXI_sendCmd(IPC_CMD9_FCLOSE, &cmdBuf, 1);
|
||||
}
|
||||
|
||||
Result fStat(const char *const path, FILINFO *const fi)
|
||||
{
|
||||
u32 cmdBuf[4];
|
||||
cmdBuf[0] = (u32)path;
|
||||
cmdBuf[1] = strlen(path) + 1;
|
||||
cmdBuf[2] = (u32)fi;
|
||||
cmdBuf[3] = sizeof(FILINFO);
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FSTAT, cmdBuf, 4);
|
||||
}
|
||||
|
||||
Result fOpenDir(DHandle *const hOut, const char *const path)
|
||||
{
|
||||
u32 cmdBuf[4];
|
||||
cmdBuf[0] = (u32)path;
|
||||
cmdBuf[1] = strlen(path) + 1;
|
||||
cmdBuf[2] = (u32)hOut;
|
||||
cmdBuf[3] = sizeof(DHandle);
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FOPEN_DIR, cmdBuf, 4);
|
||||
}
|
||||
|
||||
Result fReadDir(DHandle h, FILINFO *const fi, u32 num, u32 *const entriesRead)
|
||||
{
|
||||
u32 cmdBuf[6];
|
||||
cmdBuf[0] = (u32)fi;
|
||||
cmdBuf[1] = sizeof(FILINFO) * num;
|
||||
cmdBuf[2] = (u32)entriesRead;
|
||||
cmdBuf[3] = sizeof(u32);
|
||||
cmdBuf[4] = h;
|
||||
cmdBuf[5] = num;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FREAD_DIR, cmdBuf, 6);
|
||||
}
|
||||
|
||||
Result fCloseDir(DHandle h)
|
||||
{
|
||||
const u32 cmdBuf = h;
|
||||
return PXI_sendCmd(IPC_CMD9_FCLOSE_DIR, &cmdBuf, 1);
|
||||
}
|
||||
|
||||
Result fMkdir(const char *const path)
|
||||
{
|
||||
u32 cmdBuf[2];
|
||||
cmdBuf[0] = (u32)path;
|
||||
cmdBuf[1] = strlen(path) + 1;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FMKDIR, cmdBuf, 2);
|
||||
}
|
||||
|
||||
Result fRename(const char *const old, const char *const _new)
|
||||
{
|
||||
u32 cmdBuf[4];
|
||||
cmdBuf[0] = (u32)old;
|
||||
cmdBuf[1] = strlen(old) + 1;
|
||||
cmdBuf[2] = (u32)_new;
|
||||
cmdBuf[3] = strlen(_new) + 1;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FRENAME, cmdBuf, 4);
|
||||
}
|
||||
|
||||
Result fUnlink(const char *const path)
|
||||
{
|
||||
u32 cmdBuf[2];
|
||||
cmdBuf[0] = (u32)path;
|
||||
cmdBuf[1] = strlen(path) + 1;
|
||||
|
||||
return PXI_sendCmd(IPC_CMD9_FUNLINK, cmdBuf, 2);
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
#include "types.h"
|
||||
#include "arm11/hardware/codec.h"
|
||||
#include "arm11/hardware/spi.h"
|
||||
#include "arm11/hardware/pdn.h"
|
||||
#include "arm11/hardware/timer.h"
|
||||
#include "arm11/hardware/gpio.h"
|
||||
|
||||
|
@ -240,7 +241,7 @@ void CODEC_init(void)
|
|||
codecSwapCalibrationData(cal); // Come the fuck on. Why is this not stored in the correct endianness?
|
||||
|
||||
// General codec reset + init?
|
||||
*((vu8*)0x10141220) = 2;
|
||||
REG_PDN_I2S_CNT = PDN_I2S_CNT_I2S_CLK2_E;
|
||||
codecWriteReg(0x64, 1, 1);
|
||||
TIMER_sleepMs(40);
|
||||
codecSwitchBank(0); // What? Dummy switch after reset?
|
||||
|
@ -406,7 +407,7 @@ void CODEC_deinit(void)
|
|||
}
|
||||
*((vu16*)0x10145000) &= ~0x8000u;
|
||||
*((vu16*)0x10145002) &= ~0x8000u;
|
||||
*((vu8* )0x10141220) = 0;
|
||||
REG_PDN_I2S_CNT = 0;
|
||||
GPIO_write(GPIO_3_0, 0); // GPIO bitmask 0x40
|
||||
TIMER_sleepMs(18); // Fixed 18 ms delay when unsetting this GPIO.
|
||||
}
|
||||
|
@ -415,7 +416,7 @@ void CODEC_wakeup(void)
|
|||
{
|
||||
GPIO_write(GPIO_3_0, 1); // GPIO bitmask 0x40
|
||||
TIMER_sleepMs(10); // Fixed 10 ms delay when setting this GPIO.
|
||||
*((vu8* )0x10141220) = 2u;
|
||||
REG_PDN_I2S_CNT = PDN_I2S_CNT_I2S_CLK2_E;
|
||||
*((vu16*)0x10145000) |= 0x8000u;
|
||||
*((vu16*)0x10145002) |= 0x8000u;
|
||||
//codecMaskReg(0x64, 0x45, 0, 0x30); // Output select automatic
|
||||
|
|
|
@ -1,160 +0,0 @@
|
|||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2017 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
#include "arm11/hardware/cpu.h"
|
||||
#include "arm.h"
|
||||
#include "arm11/hardware/interrupt.h"
|
||||
#include "arm11/hardware/cfg11.h"
|
||||
#include "arm11/start.h"
|
||||
#include "util.h"
|
||||
#include "arm11/hardware/scu.h"
|
||||
|
||||
|
||||
#ifdef CORE123_INIT
|
||||
static void NAKED core23Entry(void)
|
||||
{
|
||||
__cpsid(aif);
|
||||
REG_GIC_CPU_CTRL = 1;
|
||||
|
||||
const u32 cpuId = __getCpuId();
|
||||
// Tell core 0 we are here
|
||||
if(cpuId == 3) REGs_CFG11_MPCORE_BOOTCNT[3] = 1;
|
||||
else REGs_CFG11_MPCORE_BOOTCNT[2] = 1;
|
||||
|
||||
// Wait for IPI 2 (core 2) or IPI 3 (core 3)
|
||||
u32 tmp;
|
||||
do
|
||||
{
|
||||
__wfi();
|
||||
tmp = REG_GIC_CPU_INTACK;
|
||||
REG_GIC_CPU_EOI = tmp;
|
||||
} while(tmp != cpuId);
|
||||
|
||||
// Jump to real entrypoint
|
||||
_start();
|
||||
}
|
||||
#endif
|
||||
|
||||
void core123Init(void)
|
||||
{
|
||||
if(REG_CFG11_SOCINFO & 2)
|
||||
{
|
||||
REG_GIC_CPU_CTRL = 1;
|
||||
for(u32 i = 0; i < 4; i++) REGs_GIC_DIST_ENABLE_CLEAR[i] = 0xFFFFFFFFu;
|
||||
REGs_GIC_DIST_PENDING_CLEAR[2] = 0x1000000; // Interrupt ID 88
|
||||
REGs_GIC_DIST_PRI[22] = 0;
|
||||
REGs_GIC_DIST_TARGET[22] = 1;
|
||||
REGs_GIC_DIST_ENABLE_SET[2] = 0x1000000;
|
||||
|
||||
#ifdef CORE123_INIT
|
||||
u16 clkCnt;
|
||||
// If clock modifier is 3x use clock 3x. Also enables FCRAM extension?
|
||||
if(REG_CFG11_SOCINFO & 4) clkCnt = 2<<1 | 1;
|
||||
else clkCnt = 1<<1 | 1;
|
||||
|
||||
if((REG_CFG11_MPCORE_CLKCNT & 7) != clkCnt)
|
||||
{
|
||||
// No idea what this does
|
||||
if(REG_CFG11_SOCINFO & 4) REG_CFG11_MPCORE_CNT = 0x101;
|
||||
else REG_CFG11_MPCORE_CNT = 1;
|
||||
|
||||
// Necessary delay
|
||||
wait(403);
|
||||
|
||||
CPU_setClock(clkCnt);
|
||||
REGs_GIC_DIST_PENDING_CLEAR[2] = 0x1000000;
|
||||
REG_UNK_10140400 = 3; // Clock related?
|
||||
}
|
||||
REG_UNK_10140410 = 0x3FFFF; // Clock related?
|
||||
|
||||
if((REG_SCU_CONFIG & 3) == 3)
|
||||
{
|
||||
// Set core 2/3 to normal mode (running)
|
||||
REG_SCU_CPU_STAT &= ~0b11110000;
|
||||
|
||||
const u16 clkCnt = REG_CFG11_MPCORE_CLKCNT & 7;
|
||||
u16 tmpClkCnt;
|
||||
if(REG_CFG11_SOCINFO & 4) tmpClkCnt = 0<<1 | 1;
|
||||
else tmpClkCnt = 1<<1 | 0;
|
||||
|
||||
if(clkCnt != tmpClkCnt)
|
||||
{
|
||||
CPU_setClock(tmpClkCnt);
|
||||
REGs_GIC_DIST_PENDING_CLEAR[2] = 0x1000000;
|
||||
}
|
||||
|
||||
REG_CFG11_BOOTROM_OVERLAY_CNT = 1;
|
||||
REG_CFG11_BOOTROM_OVERLAY_VAL = (u32)core23Entry;
|
||||
// If not already done enable instruction and data overlays
|
||||
if(!(REGs_CFG11_MPCORE_BOOTCNT[2] & 0x10)) REGs_CFG11_MPCORE_BOOTCNT[2] = 3;
|
||||
if(!(REGs_CFG11_MPCORE_BOOTCNT[3] & 0x10)) REGs_CFG11_MPCORE_BOOTCNT[3] = 3;
|
||||
// Wait for core 2/3 to jump out of boot11
|
||||
while((REGs_CFG11_MPCORE_BOOTCNT[2] & 0x12) != 0x10);
|
||||
while((REGs_CFG11_MPCORE_BOOTCNT[3] & 0x12) != 0x10);
|
||||
REG_CFG11_BOOTROM_OVERLAY_CNT = 0; // Disable all overlays
|
||||
|
||||
// Set clock back to original one
|
||||
if(clkCnt != tmpClkCnt) CPU_setClock(clkCnt);
|
||||
}
|
||||
|
||||
REGs_GIC_DIST_ENABLE_CLEAR[2] = 0x1000000;
|
||||
|
||||
// Wakeup core 2/3 and let them jump to their entrypoint.
|
||||
IRQ_softwareInterrupt(2, 0b0100);
|
||||
IRQ_softwareInterrupt(3, 0b1000);
|
||||
#else
|
||||
// Just enables the New 3DS FCRAM extension (if not already done)
|
||||
if((REG_CFG11_MPCORE_CLKCNT & 7) != 1) CPU_setClock(1);
|
||||
|
||||
REGs_GIC_DIST_ENABLE_CLEAR[2] = 0x1000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Wakeup core 1
|
||||
*((vu32*)0x1FFFFFDC) = (u32)_start; // Core 1 entrypoint
|
||||
IRQ_softwareInterrupt(1, 0b0010);
|
||||
}
|
||||
|
||||
void CPU_setClock(u16 clk)
|
||||
{
|
||||
REG_CFG11_MPCORE_CLKCNT = 0x8000 | (clk & 7);
|
||||
do
|
||||
{
|
||||
__wfi();
|
||||
} while(!(REG_CFG11_MPCORE_CLKCNT & 0x8000));
|
||||
}
|
||||
|
||||
void CPU_poweroffCore23(void)
|
||||
{
|
||||
if(REG_CFG11_SOCINFO & 2)
|
||||
{
|
||||
REGs_CFG11_MPCORE_BOOTCNT[2] = 0;
|
||||
REGs_CFG11_MPCORE_BOOTCNT[3] = 0;
|
||||
|
||||
REG_UNK_10140410 = 0;
|
||||
REG_UNK_10140400 = 0;
|
||||
|
||||
REG_CFG11_MPCORE_CNT = 0;
|
||||
CPU_setClock(1);
|
||||
|
||||
REG_SCU_CPU_STAT |= 0b1111<<4;
|
||||
|
||||
CPU_setClock(0);
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@
|
|||
#include "types.h"
|
||||
#include "fb_assert.h"
|
||||
#include "hardware/gfx.h"
|
||||
#include "arm11/hardware/cfg11.h"
|
||||
#include "arm11/hardware/pdn.h"
|
||||
#include "arm11/hardware/lcd.h"
|
||||
#include "arm11/hardware/gx.h"
|
||||
#include "arm11/hardware/gpu_regs.h"
|
||||
|
@ -36,10 +38,6 @@
|
|||
#include "arm11/allocator/vram.h"
|
||||
|
||||
|
||||
#define PDN_REGS_BASE (IO_MEM_ARM9_ARM11 + 0x40000)
|
||||
#define REG_PDN_GPU_CNT *((vu32*)(PDN_REGS_BASE + 0x1200))
|
||||
|
||||
|
||||
static struct
|
||||
{
|
||||
u16 lcdIds; // Bits 0-7 top screen, 8-15 bottom screen.
|
||||
|
@ -70,12 +68,12 @@ void GFX_init(GfxFbFmt fmtTop, GfxFbFmt fmtBot)
|
|||
g_gfxState.doubleBuf[0] = 1;
|
||||
g_gfxState.doubleBuf[1] = 1;
|
||||
|
||||
*((vu32*)0x10140140) = 0; // REG_CFG11_GPUPROT
|
||||
REG_CFG11_GPUPROT = 0;
|
||||
|
||||
// Reset
|
||||
REG_PDN_GPU_CNT = 0x10000;
|
||||
REG_PDN_GPU_CNT = PDN_GPU_CNT_CLK_E;
|
||||
wait(12);
|
||||
REG_PDN_GPU_CNT = 0x1007F;
|
||||
REG_PDN_GPU_CNT = PDN_GPU_CNT_CLK_E | PDN_GPU_CNT_RST_ALL;
|
||||
REG_GX_GPU_CLK = 0x100;
|
||||
REG_GX_PSC_VRAM = 0;
|
||||
REG_GX_PSC_FILL0_CNT = 0;
|
||||
|
@ -178,7 +176,7 @@ void GFX_deinit(void)
|
|||
REG_LCD_RST = 0;
|
||||
REG_GX_PSC_VRAM = 0xF00;
|
||||
REG_GX_GPU_CLK = 0;
|
||||
REG_PDN_GPU_CNT = 0x10001;
|
||||
REG_PDN_GPU_CNT = PDN_GPU_CNT_CLK_E | PDN_GPU_CNT_RST_REGS;
|
||||
|
||||
deallocFramebufs();
|
||||
|
||||
|
@ -551,12 +549,12 @@ void GX_processCommandList(u32 size, const u32 *const cmdList)
|
|||
REG_LCD_PDC1_SWAP = 0x70100;
|
||||
|
||||
REG_GX_PSC_VRAM = 0xF00;
|
||||
REG_PDN_GPU_CNT = 0x7F;
|
||||
REG_PDN_GPU_CNT = PDN_GPU_CNT_RST_ALL;
|
||||
}
|
||||
|
||||
void GFX_returnFromLowPowerState(void)
|
||||
{
|
||||
REG_PDN_GPU_CNT = 0x1007F;
|
||||
REG_PDN_GPU_CNT = PDN_GPU_CNT_CLK_E | PDN_GPU_CNT_RST_ALL;
|
||||
REG_GX_PSC_VRAM = 0;
|
||||
//REG_GX_GPU_CLK = 0x70100;
|
||||
REG_GX_PSC_FILL0_CNT = 0;
|
||||
|
|
|
@ -180,7 +180,7 @@ void IRQ_init(void)
|
|||
REG_GIC_CPU_BINPOINT = 3; // All priority bits are compared for pre-emption.
|
||||
REG_GIC_CPU_CTRL = 1; // Enable the interrupt interface for this CPU.
|
||||
|
||||
REG_CFG11_FIQ_CNT = 2; // Disable FIQs.
|
||||
REG_CFG11_FIQ_MASK = FIQ_MASK_CPU3 | FIQ_MASK_CPU2 | FIQ_MASK_CPU1 | FIQ_MASK_CPU0; // Disable FIQs.
|
||||
}
|
||||
|
||||
void IRQ_registerIsr(Interrupt id, u8 prio, u8 cpuMask, IrqIsr isr)
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
#include <string.h>
|
||||
#include "types.h"
|
||||
#include "hardware/lgy.h"
|
||||
#include "hardware/pxi.h"
|
||||
#include "ipc_handler.h"
|
||||
#include "arm11/hardware/hid.h"
|
||||
#include "arm11/hardware/interrupt.h"
|
||||
#include "fs.h"
|
||||
#include "hardware/cache.h"
|
||||
#include "arm11/hardware/pdn.h"
|
||||
#include "arm11/hardware/mcu.h"
|
||||
#include "arm11/hardware/lgyfb.h"
|
||||
|
||||
|
@ -34,12 +38,62 @@ static void lgySleepIrqHandler(u32 intSource)
|
|||
}
|
||||
}
|
||||
|
||||
Result LGY_prepareGbaMode(bool gbaBios, u16 saveType)
|
||||
static Result loadRom(const char *const path)
|
||||
{
|
||||
const u32 cmdBuf[2] = {gbaBios, saveType};
|
||||
Result res = PXI_sendCmd(IPC_CMD9_PREPARE_GBA, cmdBuf, 2);
|
||||
Result res;
|
||||
FHandle f;
|
||||
if((res = fOpen(&f, path, FA_OPEN_EXISTING | FA_READ)) == RES_OK)
|
||||
{
|
||||
u32 romSize;
|
||||
if((romSize = fSize(f)) <= MAX_ROM_SIZE)
|
||||
{
|
||||
u8 *ptr = (u8*)ROM_LOC;
|
||||
u32 read;
|
||||
while((res = fRead(f, ptr, 0x100000u, &read)) == RES_OK && read == 0x100000u)
|
||||
ptr += 0x100000u;
|
||||
|
||||
if(res == RES_OK)
|
||||
{
|
||||
// Pad ROM area with "open bus" value.
|
||||
memset((void*)(ROM_LOC + romSize), 0xFFFFFFFFu, romSize);
|
||||
}
|
||||
}
|
||||
else res = RES_ROM_TOO_BIG;
|
||||
|
||||
fClose(f);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void setupFcramForGbaMode(void)
|
||||
{
|
||||
// FCRAM reset and clock disable.
|
||||
flushDCache();
|
||||
// TODO: Unmap FCRAM.
|
||||
while(!REG_LGY_MODE); // Wait until legacy mode is ready.
|
||||
*((vu32*)0x10201000) &= ~1u; // Some kind of bug fix for the GBA cart emu?
|
||||
REG_PDN_FCRAM_CNT = 0; // Set reset low (active).
|
||||
REG_PDN_FCRAM_CNT = PDN_FCRAM_CNT_RST; // Take it out of reset.
|
||||
while(REG_PDN_FCRAM_CNT & PDN_FCRAM_CNT_CLK_E_ACK); // Wait until clock is disabled.
|
||||
}
|
||||
|
||||
Result LGY_prepareGbaMode(bool gbaBios, u16 saveType, const char *const romPath, const char *const savePath)
|
||||
{
|
||||
// Load the ROM image.
|
||||
Result res = loadRom(romPath);
|
||||
if(res != RES_OK) return res;
|
||||
|
||||
// Prepare ARM9 for GBA mode + settings and save loading.
|
||||
u32 cmdBuf[2];
|
||||
cmdBuf[0] = (u32)savePath;
|
||||
cmdBuf[1] = strlen(savePath) + 1;
|
||||
cmdBuf[2] = gbaBios;
|
||||
cmdBuf[3] = saveType;
|
||||
res = PXI_sendCmd(IPC_CMD9_PREPARE_GBA, cmdBuf, 4);
|
||||
if(res != RES_OK) return res;
|
||||
|
||||
// Setup GBA Real-Time Clock.
|
||||
GbaRtc rtc;
|
||||
MCU_getRTCTime((u8*)&rtc);
|
||||
rtc.time = __builtin_bswap32(rtc.time)>>8;
|
||||
|
@ -47,18 +101,13 @@ Result LGY_prepareGbaMode(bool gbaBios, u16 saveType)
|
|||
// TODO: Do we need to set day of week?
|
||||
LGY_setGbaRtc(rtc);
|
||||
|
||||
// Setup Legacy Framebuffer.
|
||||
LGYFB_init();
|
||||
|
||||
//flushInvalidateDCache();
|
||||
// Unmap FCRAM if mapped.
|
||||
|
||||
while(!REG_LGY_MODE); // Wait until legacy mode is ready.
|
||||
// FCRAM reset and disable.
|
||||
*((vu32*)0x10201000) &= ~1u; // Disable DRAM controller? If bit 0 set below reg pokes do nothing.
|
||||
*((vu8*)0x10141210) = 0; // Set reset low (active).
|
||||
*((vu8*)0x10141210) = 1; // Take it out of reset.
|
||||
while(*((vu8*)0x10141210) & 4u); // Wait for acknowledge?
|
||||
// Setup FCRAM for GBA mode.
|
||||
setupFcramForGbaMode();
|
||||
|
||||
// Setup IRQ handlers and sleep mode handling.
|
||||
REG_LGY_SLEEP = 1u<<15;
|
||||
IRQ_registerIsr(IRQ_LGY_SLEEP, 14, 0, lgySleepIrqHandler);
|
||||
IRQ_registerIsr(IRQ_HID_PADCNT, 14, 0, lgySleepIrqHandler);
|
||||
|
@ -79,13 +128,7 @@ Result LGY_getGbaRtc(GbaRtc *const out)
|
|||
|
||||
void LGY_switchMode(void)
|
||||
{
|
||||
//ee_puts("Done. Doing final switch...");
|
||||
//updateScreens();
|
||||
/*do
|
||||
{
|
||||
while(*((vu16*)0x10008004) & 0x100u); // ARM9 reg pokes
|
||||
} while(*((vu32*)0x1000800C) != 0x...);*/
|
||||
REG_LGY_MODE = 0x8000u;
|
||||
REG_LGY_MODE = LGY_MODE_START;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
|
@ -126,12 +169,17 @@ void LGY_handleEvents(void)
|
|||
//if(REG_LGY_SLEEP & 2u) REG_HID_PADCNT = REG_LGY_PADCNT;
|
||||
}
|
||||
|
||||
Result LGY_backupGbaSave(void)
|
||||
{
|
||||
return PXI_sendCmd(IPC_CMD9_BACKUP_GBA_SAVE, NULL, 0);
|
||||
}
|
||||
|
||||
void LGY_deinit(void)
|
||||
{
|
||||
//REG_LGY_PAD_VAL = 0x1FFF; // Force all buttons not pressed.
|
||||
//REG_LGY_PAD_SEL = 0x1FFF;
|
||||
|
||||
// TODO: Tell ARM9 to backup the savegame instead of handling it on poweroff.
|
||||
LGY_backupGbaSave();
|
||||
LGYFB_deinit();
|
||||
|
||||
IRQ_unregisterIsr(IRQ_LGY_SLEEP);
|
||||
|
|
|
@ -214,8 +214,8 @@ void setupMmu(void)
|
|||
L1_TO_L2(ATTR_NORM_WRITE_BACK_ALLOC));
|
||||
|
||||
// FCRAM with New 3DS extension
|
||||
//mmuMapSupersections(FCRAM_BASE, FCRAM_BASE, 16, PERM_PRIV_RW_USR_NA, true,
|
||||
// ATTR_NORM_WRITE_BACK_ALLOC);
|
||||
mmuMapSupersections(FCRAM_BASE, FCRAM_BASE, 16, PERM_PRIV_RW_USR_NA, true,
|
||||
ATTR_NORM_WRITE_BACK_ALLOC);
|
||||
|
||||
// Map fastboot executable start to boot11 mirror (exception vectors)
|
||||
mmuMapPages(BOOT11_MIRROR2, (u32)__start__, 1, mmuTables->l2Boot11, true,
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2020 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
#include "arm11/hardware/pdn.h"
|
||||
#include "arm11/hardware/cfg11.h"
|
||||
#include "arm.h"
|
||||
#include "arm11/hardware/interrupt.h"
|
||||
#include "arm11/start.h"
|
||||
#include "util.h"
|
||||
#include "arm11/hardware/scu.h"
|
||||
|
||||
|
||||
|
||||
#ifdef CORE123_INIT
|
||||
static void NAKED core23Entry(void)
|
||||
{
|
||||
__cpsid(aif);
|
||||
REG_GIC_CPU_CTRL = 1;
|
||||
|
||||
const u32 cpuId = __getCpuId();
|
||||
// Tell core 0 we are here
|
||||
if(cpuId == 3) REGs_PDN_MPCORE_BOOTCNT[3] = MPCORE_BOOTCNT_RST;
|
||||
else REGs_PDN_MPCORE_BOOTCNT[2] = MPCORE_BOOTCNT_RST;
|
||||
|
||||
// Wait for IPI 2 (core 2) or IPI 3 (core 3)
|
||||
u32 tmp;
|
||||
do
|
||||
{
|
||||
__wfi();
|
||||
tmp = REG_GIC_CPU_INTACK;
|
||||
REG_GIC_CPU_EOI = tmp;
|
||||
} while(tmp != cpuId);
|
||||
|
||||
// Jump to real entrypoint
|
||||
_start();
|
||||
}
|
||||
#endif
|
||||
|
||||
void PDN_core123Init(void)
|
||||
{
|
||||
if(REG_CFG11_SOCINFO & SOCINFO_N3DS_PROTO)
|
||||
{
|
||||
REG_GIC_CPU_CTRL = 1;
|
||||
for(u32 i = 0; i < 4; i++) REGs_GIC_DIST_ENABLE_CLEAR[i] = 0xFFFFFFFFu;
|
||||
REGs_GIC_DIST_PENDING_CLEAR[2] = 0x1000000; // Interrupt ID 88
|
||||
REGs_GIC_DIST_PRI[22] = 0;
|
||||
REGs_GIC_DIST_TARGET[22] = 1;
|
||||
REGs_GIC_DIST_ENABLE_SET[2] = 0x1000000;
|
||||
|
||||
#ifdef CORE123_INIT
|
||||
u16 socmode;
|
||||
// If non-prototype SoC use 804 MHz.
|
||||
if(REG_CFG11_SOCINFO & SOCINFO_N3DS) socmode = SOCMODE_N3DS_804MHz;
|
||||
else socmode = SOCMODE_N3DS_PROTO_536MHz;
|
||||
|
||||
if((REG_PDN_MPCORE_SOCMODE & SOCMODE_MASK) != socmode)
|
||||
{
|
||||
// No idea what this does
|
||||
if(REG_CFG11_SOCINFO & SOCINFO_N3DS) REG_PDN_MPCORE_CNT = PDN_MPCORE_CNT_L2C_E | PDN_MPCORE_CNT_MEM_EXT_E;
|
||||
else REG_PDN_MPCORE_CNT = PDN_MPCORE_CNT_MEM_EXT_E;
|
||||
|
||||
// Necessary delay.
|
||||
wait(403);
|
||||
|
||||
PDN_setSocmode(socmode);
|
||||
REGs_GIC_DIST_PENDING_CLEAR[2] = 0x1000000;
|
||||
REG_CFG11_GPU_N3DS_CNT = GPU_N3DS_CNT_TEX_FIX | GPU_N3DS_CNT_N3DS_MODE;
|
||||
}
|
||||
REG_CFG11_CDMA_PERIPHERALS = CDMA_PERIPHERALS_ALL; // Redirect all to CDMA2.
|
||||
|
||||
if((REG_SCU_CONFIG & 3) == 3)
|
||||
{
|
||||
// Set core 2/3 to normal mode (running)
|
||||
REG_SCU_CPU_STAT &= ~0b11110000;
|
||||
|
||||
const u16 socmode = REG_PDN_MPCORE_SOCMODE & SOCMODE_MASK;
|
||||
u16 tmpSocmode;
|
||||
if(REG_CFG11_SOCINFO & SOCINFO_N3DS) tmpSocmode = SOCMODE_N3DS_268MHz;
|
||||
else tmpSocmode = SOCMODE_N3DS_PROTO_268MHz;
|
||||
|
||||
if(socmode != tmpSocmode)
|
||||
{
|
||||
PDN_setSocmode(tmpSocmode);
|
||||
REGs_GIC_DIST_PENDING_CLEAR[2] = 0x1000000;
|
||||
}
|
||||
|
||||
REG_CFG11_BOOTROM_OVERLAY_CNT = BOOTROM_OVERLAY_CNT_E;
|
||||
REG_CFG11_BOOTROM_OVERLAY_VAL = (u32)core23Entry;
|
||||
// If not already done enable instruction and data overlays
|
||||
if(!(REGs_PDN_MPCORE_BOOTCNT[2] & MPCORE_BOOTCNT_RST_STAT))
|
||||
{
|
||||
REGs_PDN_MPCORE_BOOTCNT[2] = MPCORE_BOOTCNT_D_OVERL_E | MPCORE_BOOTCNT_RST;
|
||||
}
|
||||
if(!(REGs_PDN_MPCORE_BOOTCNT[3] & MPCORE_BOOTCNT_RST_STAT))
|
||||
{
|
||||
REGs_PDN_MPCORE_BOOTCNT[3] = MPCORE_BOOTCNT_D_OVERL_E | MPCORE_BOOTCNT_RST;
|
||||
}
|
||||
// Wait for core 2/3 to jump out of boot11
|
||||
while((REGs_PDN_MPCORE_BOOTCNT[2] & (MPCORE_BOOTCNT_RST_STAT | MPCORE_BOOTCNT_D_OVERL_E))
|
||||
!= MPCORE_BOOTCNT_RST_STAT);
|
||||
while((REGs_PDN_MPCORE_BOOTCNT[3] & (MPCORE_BOOTCNT_RST_STAT | MPCORE_BOOTCNT_D_OVERL_E))
|
||||
!= MPCORE_BOOTCNT_RST_STAT);
|
||||
REG_CFG11_BOOTROM_OVERLAY_CNT = 0; // Disable all overlays
|
||||
|
||||
// Set clock back to original one
|
||||
if(socmode != tmpSocmode) PDN_setSocmode(socmode);
|
||||
}
|
||||
|
||||
REGs_GIC_DIST_ENABLE_CLEAR[2] = 0x1000000;
|
||||
|
||||
// Wakeup core 2/3 and let them jump to their entrypoint.
|
||||
IRQ_softwareInterrupt(2, 0b0100);
|
||||
IRQ_softwareInterrupt(3, 0b1000);
|
||||
#else
|
||||
// Just enables the New3DS FCRAM extension (if not already done).
|
||||
if((REG_PDN_MPCORE_SOCMODE & SOCMODE_MASK) != SOCMODE_N3DS_268MHz)
|
||||
PDN_setSocmode(SOCMODE_N3DS_268MHz);
|
||||
|
||||
REGs_GIC_DIST_ENABLE_CLEAR[2] = 0x1000000;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Wakeup core 1
|
||||
*((vu32*)0x1FFFFFDC) = (u32)_start; // Core 1 entrypoint
|
||||
IRQ_softwareInterrupt(1, 0b0010);
|
||||
}
|
||||
|
||||
void PDN_setSocmode(PdnSocmode socmode)
|
||||
{
|
||||
REG_PDN_MPCORE_SOCMODE = PDN_MPCORE_SOCMODE_ACK | socmode;
|
||||
do
|
||||
{
|
||||
__wfi();
|
||||
} while(!(REG_PDN_MPCORE_SOCMODE & PDN_MPCORE_SOCMODE_ACK));
|
||||
}
|
||||
|
||||
void PDN_poweroffCore23(void)
|
||||
{
|
||||
if(REG_CFG11_SOCINFO & SOCINFO_N3DS_PROTO)
|
||||
{
|
||||
REGs_PDN_MPCORE_BOOTCNT[2] = 0;
|
||||
REGs_PDN_MPCORE_BOOTCNT[3] = 0;
|
||||
|
||||
REG_CFG11_CDMA_PERIPHERALS = 0;
|
||||
REG_CFG11_GPU_N3DS_CNT = 0;
|
||||
|
||||
REG_PDN_MPCORE_CNT = 0;
|
||||
PDN_setSocmode(SOCMODE_N3DS_268MHz);
|
||||
|
||||
REG_SCU_CPU_STAT |= 0b1111<<4;
|
||||
|
||||
PDN_setSocmode(SOCMODE_O3DS_268MHz);
|
||||
}
|
||||
}
|
|
@ -112,7 +112,7 @@ void NSPI_init(void)
|
|||
inited = true;
|
||||
|
||||
// Switch all 3 buses to the new interface
|
||||
REG_CFG11_SPI_CNT = 1u<<2 | 1u<<1 | 1u;
|
||||
REG_CFG11_SPI_CNT = SPI_CNT_SPI3_NEW_IF | SPI_CNT_SPI2_NEW_IF | SPI_CNT_SPI1_NEW_IF;
|
||||
|
||||
SpiRegs *regs = nspiGetBusRegsBase(SPI_BUS1);
|
||||
regs->NSPI_INT_MASK = NSPI_INT_TRANSF_END; // Disable interrupt 1
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "arm11/fmt.h"
|
||||
#include "arm11/power.h"
|
||||
#include "hardware/gfx.h"
|
||||
#include "fs.h"
|
||||
#include "arm.h"
|
||||
|
||||
|
||||
|
@ -34,10 +35,11 @@ int main(void)
|
|||
GFX_setBrightness(DEFAULT_BRIGHTNESS, DEFAULT_BRIGHTNESS);
|
||||
consoleInit(SCREEN_BOT, NULL);
|
||||
//CODEC_init();
|
||||
fMount(FS_DRIVE_SDMC);
|
||||
|
||||
ee_puts("Reading ROM and save...");
|
||||
Result res;
|
||||
if((res = LGY_prepareGbaMode(false, SAVE_TYPE_SRAM_256k)) == RES_OK)
|
||||
if((res = LGY_prepareGbaMode(false, SAVE_TYPE_SRAM_256k, "sdmc:/rom.gba", "sdmc:/rom.sav")) == RES_OK)
|
||||
{
|
||||
#ifdef NDEBUG
|
||||
GFX_setForceBlack(false, true);
|
||||
|
@ -60,6 +62,7 @@ int main(void)
|
|||
else printErrorWaitInput(res, 0);
|
||||
|
||||
LGY_deinit();
|
||||
fUnmount(FS_DRIVE_SDMC);
|
||||
CODEC_deinit();
|
||||
GFX_deinit();
|
||||
power_off();
|
||||
|
|
|
@ -125,7 +125,7 @@ BEGIN_ASM_FUNC _start
|
|||
ldr r1, =fake_heap_end
|
||||
str r0, [r1]
|
||||
blx __libc_init_array @ Initialize ctors and dtors
|
||||
blx core123Init
|
||||
blx PDN_core123Init
|
||||
_start_skip_bss_init_array:
|
||||
ldrh r2, =0x706 @ Disable + reset all counters. Cycle counter divider 1. IRQs disabled.
|
||||
mcr p15, 0, r2, c15, c12, 0 @ Write Performance Monitor Control Register
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
#include "arm11/hardware/i2c.h"
|
||||
#include "arm11/hardware/mcu.h"
|
||||
#include "arm11/hardware/hid.h"
|
||||
#include "arm11/hardware/cpu.h"
|
||||
#include "arm.h"
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,274 @@
|
|||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2017 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
#include "types.h"
|
||||
#include "error_codes.h"
|
||||
#include "util.h"
|
||||
#include "fs.h"
|
||||
#include "arm9/debug.h"
|
||||
#include "fatfs/ff.h"
|
||||
|
||||
|
||||
static const char *const g_fsPathTable[FS_MAX_DRIVES] = {FS_DRIVE_NAMES};
|
||||
static struct
|
||||
{
|
||||
FATFS fsTable[FS_MAX_DRIVES];
|
||||
|
||||
FIL fTable[FS_MAX_FILES];
|
||||
u32 fBitmap;
|
||||
u32 fHandles;
|
||||
|
||||
DIR dTable[FS_MAX_DIRS];
|
||||
u32 dBitmap;
|
||||
u32 dHandles;
|
||||
} g_fsState = {0};
|
||||
|
||||
|
||||
|
||||
static Result fres2Res(FRESULT fr)
|
||||
{
|
||||
if(fr != FR_OK) return fr + 2;
|
||||
else return RES_OK;
|
||||
}
|
||||
|
||||
static u32 findUnusedFileSlot(void)
|
||||
{
|
||||
if(g_fsState.fHandles >= FS_MAX_FILES) return (u32)-1;
|
||||
|
||||
u32 i = 0;
|
||||
do
|
||||
{
|
||||
if((g_fsState.fBitmap & 1u<<i) == 0) break;
|
||||
} while(++i < FS_MAX_FILES);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static bool isFileHandleValid(FHandle h)
|
||||
{
|
||||
if(h > g_fsState.fHandles) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
static u32 findUnusedDirSlot(void)
|
||||
{
|
||||
if(g_fsState.dHandles >= FS_MAX_DIRS) return (u32)-1;
|
||||
|
||||
u32 i = 0;
|
||||
do
|
||||
{
|
||||
if((g_fsState.dBitmap & 1u<<i) == 0) break;
|
||||
} while(++i < FS_MAX_DIRS);
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static bool isDirHandleValid(DHandle h)
|
||||
{
|
||||
if(h > g_fsState.dHandles) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
Result fMount(FsDrive drive)
|
||||
{
|
||||
if(drive >= FS_MAX_DRIVES) return RES_FR_INVALID_DRIVE;
|
||||
|
||||
return fres2Res(f_mount(&g_fsState.fsTable[drive], g_fsPathTable[drive], 1));
|
||||
}
|
||||
|
||||
Result fUnmount(FsDrive drive)
|
||||
{
|
||||
if(drive >= FS_MAX_DRIVES) return RES_FR_INVALID_DRIVE;
|
||||
|
||||
return fres2Res(f_mount(NULL, g_fsPathTable[drive], 0));
|
||||
}
|
||||
|
||||
Result fGetFree(FsDrive drive, u64 *const size)
|
||||
{
|
||||
if(drive >= FS_MAX_DRIVES) return RES_FR_INVALID_DRIVE;
|
||||
|
||||
DWORD freeClusters;
|
||||
FATFS *fs;
|
||||
Result res = fres2Res(f_getfree(g_fsPathTable[drive], &freeClusters, &fs));
|
||||
if(res == RES_OK)
|
||||
{
|
||||
if(size) *size = (u64)(freeClusters * fs->csize) * 512u;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fOpen(FHandle *const hOut, const char *const path, u8 mode)
|
||||
{
|
||||
if(hOut == NULL) return RES_INVALID_ARG;
|
||||
const u32 slot = findUnusedFileSlot();
|
||||
if(slot == (u32)-1) return RES_FR_TOO_MANY_OPEN_FILES;
|
||||
|
||||
Result res = fres2Res(f_open(&g_fsState.fTable[slot], path, mode));
|
||||
if(res == RES_OK)
|
||||
{
|
||||
g_fsState.fBitmap |= 1u<<slot;
|
||||
g_fsState.fHandles++;
|
||||
*hOut = (FHandle)slot;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fRead(FHandle h, void *const buf, u32 size, u32 *const bytesRead)
|
||||
{
|
||||
if(!isFileHandleValid(h)) return RES_FR_INVALID_OBJECT;
|
||||
|
||||
UINT tmpBytesRead;
|
||||
Result res = fres2Res(f_read(&g_fsState.fTable[h], buf, size, &tmpBytesRead));
|
||||
|
||||
if(bytesRead != NULL) *bytesRead = tmpBytesRead;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fWrite(FHandle h, const void *const buf, u32 size, u32 *const bytesWritten)
|
||||
{
|
||||
if(!isFileHandleValid(h)) return RES_FR_INVALID_OBJECT;
|
||||
|
||||
UINT tmpBytesWritten;
|
||||
Result res = fres2Res(f_write(&g_fsState.fTable[h], buf, size, &tmpBytesWritten));
|
||||
|
||||
if(bytesWritten != NULL) *bytesWritten = tmpBytesWritten;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fSync(FHandle h)
|
||||
{
|
||||
if(!isFileHandleValid(h)) return RES_FR_INVALID_OBJECT;
|
||||
|
||||
return fres2Res(f_sync(&g_fsState.fTable[h]));
|
||||
}
|
||||
|
||||
Result fLseek(FHandle h, u32 off)
|
||||
{
|
||||
if(!isFileHandleValid(h)) return RES_FR_INVALID_OBJECT;
|
||||
|
||||
return fres2Res(f_lseek(&g_fsState.fTable[h], off));
|
||||
}
|
||||
|
||||
u32 fTell(FHandle h)
|
||||
{
|
||||
if(!isFileHandleValid(h)) return 0;
|
||||
|
||||
return f_tell(&g_fsState.fTable[h]);
|
||||
}
|
||||
|
||||
u32 fSize(FHandle h)
|
||||
{
|
||||
if(!isFileHandleValid(h)) return 0;
|
||||
|
||||
return f_size(&g_fsState.fTable[h]);
|
||||
}
|
||||
|
||||
Result fClose(FHandle h)
|
||||
{
|
||||
if(g_fsState.fHandles == 0 || !isFileHandleValid(h))
|
||||
return RES_FR_INVALID_OBJECT;
|
||||
|
||||
Result res = fres2Res(f_close(&g_fsState.fTable[h]));
|
||||
g_fsState.fBitmap &= ~(1u<<h);
|
||||
g_fsState.fHandles--;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fStat(const char *const path, FILINFO *const fi)
|
||||
{
|
||||
return fres2Res(f_stat(path, fi));
|
||||
}
|
||||
|
||||
Result fOpenDir(DHandle *const hOut, const char *const path)
|
||||
{
|
||||
if(hOut == NULL) return RES_INVALID_ARG;
|
||||
const u32 slot = findUnusedDirSlot();
|
||||
if(slot == (u32)-1) return RES_FR_TOO_MANY_OPEN_FILES;
|
||||
|
||||
Result res = fres2Res(f_opendir(&g_fsState.dTable[slot], path));
|
||||
if(res == RES_OK)
|
||||
{
|
||||
g_fsState.dBitmap |= 1u<<slot;
|
||||
g_fsState.dHandles++;
|
||||
*hOut = (DHandle)slot;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fReadDir(DHandle h, FILINFO *const fi, u32 num, u32 *const entriesRead)
|
||||
{
|
||||
if(!isDirHandleValid(h)) return RES_FR_INVALID_OBJECT;
|
||||
// TODO: Check for insanely high num?
|
||||
|
||||
u32 i;
|
||||
DIR *const dir = &g_fsState.dTable[h];
|
||||
Result res;
|
||||
for(i = 0; i < num; i++)
|
||||
{
|
||||
res = fres2Res(f_readdir(dir, &fi[i]));
|
||||
|
||||
if(res != RES_OK || fi[i].fname[0] == 0) break;
|
||||
}
|
||||
|
||||
if(entriesRead != NULL) *entriesRead = i;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fCloseDir(DHandle h)
|
||||
{
|
||||
if(g_fsState.dHandles == 0 || !isDirHandleValid(h))
|
||||
return RES_FR_INVALID_OBJECT;
|
||||
|
||||
Result res = fres2Res(f_closedir(&g_fsState.dTable[h]));
|
||||
g_fsState.dBitmap &= ~(1u<<h);
|
||||
g_fsState.dHandles--;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Result fMkdir(const char *const path)
|
||||
{
|
||||
return fres2Res(f_mkdir(path));
|
||||
}
|
||||
|
||||
Result fRename(const char *const old, const char *const _new)
|
||||
{
|
||||
return fres2Res(f_rename(old, _new));
|
||||
}
|
||||
|
||||
Result fUnlink(const char *const path)
|
||||
{
|
||||
return fres2Res(f_unlink(path));
|
||||
}
|
||||
|
||||
void fsDeinit(void)
|
||||
{
|
||||
for(u32 i = 0; i < FS_MAX_FILES; i++) fClose(i);
|
||||
for(u32 i = 0; i < FS_MAX_DRIVES; i++) fUnmount(i);
|
||||
|
||||
// TODO: Deinit drives.
|
||||
}
|
|
@ -6,9 +6,10 @@
|
|||
#include "mmio.h"
|
||||
#include "arm9/hardware/ndma.h"
|
||||
#include "arm9/arm7_stub.h"
|
||||
#include "fatfs/ff.h"
|
||||
#include "fs.h"
|
||||
#include "arm9/debug.h"
|
||||
#include "arm9/hardware/crypto.h"
|
||||
#include "util.h"
|
||||
|
||||
|
||||
#define LGY_REGS_BASE (IO_MEM_ARM9_ONLY + 0x18000)
|
||||
|
@ -24,17 +25,9 @@
|
|||
#define REGs_LGY_GBA_SAVE_TIMING ((vu32*)(LGY_REGS_BASE + 0x120))
|
||||
|
||||
|
||||
#define MAX_ROM_SIZE (1024u * 1024 * 32)
|
||||
#define MAX_SAVE_SIZE (1024u * 128)
|
||||
#define ARM7_STUB_LOC (0x3007E00u)
|
||||
#define ARM7_STUB_LOC9 (0x80BFE00u)
|
||||
#define ROM_LOC (0x20000000u)
|
||||
#define SAVE_LOC (0x8080000u)
|
||||
|
||||
|
||||
static FATFS g_sd = {0};
|
||||
static u32 g_saveSize = 0;
|
||||
static u32 g_saveHash[8] = {0};
|
||||
static char g_savePath[256] = {0};
|
||||
|
||||
|
||||
|
||||
|
@ -80,7 +73,7 @@ static void setupSaveType(u16 saveType)
|
|||
iomemcpy(REGs_LGY_GBA_SAVE_TIMING, saveTm, 16);
|
||||
}
|
||||
|
||||
Result LGY_prepareGbaMode(bool gbaBios, u16 saveType)
|
||||
Result LGY_prepareGbaMode(bool gbaBios, u16 saveType, const char *const savePath)
|
||||
{
|
||||
REG_LGY_MODE = LGY_MODE_AGB;
|
||||
|
||||
|
@ -88,73 +81,23 @@ Result LGY_prepareGbaMode(bool gbaBios, u16 saveType)
|
|||
setupSaveType(saveType);
|
||||
|
||||
Result res = RES_OK;
|
||||
do
|
||||
if(g_saveSize != 0)
|
||||
{
|
||||
u32 romSize = 0;
|
||||
if(f_mount(&g_sd, "sdmc:", 1) == FR_OK)
|
||||
FHandle f;
|
||||
if(fOpen(&f, savePath, FA_OPEN_EXISTING | FA_READ) == RES_OK)
|
||||
{
|
||||
FIL f;
|
||||
if(f_open(&f, "sdmc:/rom.gba", FA_OPEN_EXISTING | FA_READ) == FR_OK)
|
||||
// TODO: Should we handle 0 byte files?
|
||||
res = fRead(f, (void*)SAVE_LOC, MAX_SAVE_SIZE, NULL);
|
||||
fClose(f);
|
||||
|
||||
if(res == RES_OK)
|
||||
{
|
||||
if((romSize = f_size(&f)) > MAX_ROM_SIZE)
|
||||
{
|
||||
f_close(&f);
|
||||
res = RES_ROM_TOO_BIG;
|
||||
break;
|
||||
}
|
||||
|
||||
u8 *ptr = (u8*)ROM_LOC;
|
||||
UINT read;
|
||||
FRESULT fres;
|
||||
while((fres = f_read(&f, ptr, 0x100000u, &read)) == FR_OK && read == 0x100000u)
|
||||
ptr += 0x100000u;
|
||||
|
||||
f_close(&f);
|
||||
|
||||
if(fres != FR_OK)
|
||||
{
|
||||
res = RES_FILE_READ_ERR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = RES_FILE_OPEN_ERR;
|
||||
break;
|
||||
}
|
||||
|
||||
if(g_saveSize != 0)
|
||||
{
|
||||
if(f_open(&f, "sdmc:/rom.sav", FA_OPEN_EXISTING | FA_READ) == FR_OK)
|
||||
{
|
||||
UINT read;
|
||||
FRESULT fres = f_read(&f, (void*)SAVE_LOC, MAX_SAVE_SIZE, &read);
|
||||
|
||||
f_close(&f);
|
||||
|
||||
if(fres != FR_OK)
|
||||
{
|
||||
res = RES_FILE_READ_ERR;
|
||||
break;
|
||||
}
|
||||
|
||||
sha((u32*)SAVE_LOC, g_saveSize, g_saveHash, SHA_INPUT_BIG | SHA_MODE_256, SHA_OUTPUT_BIG);
|
||||
}
|
||||
else NDMA_fill((u32*)SAVE_LOC, 0xFFFFFFFFu, g_saveSize);
|
||||
sha((u32*)SAVE_LOC, g_saveSize, g_saveHash, SHA_INPUT_BIG | SHA_MODE_256, SHA_OUTPUT_BIG);
|
||||
strncpy_s(g_savePath, savePath, 255, 256);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
res = RES_MOUNT_ERR;
|
||||
break;
|
||||
}
|
||||
|
||||
// Pad ROM area with "open bus" value.
|
||||
if(romSize < MAX_ROM_SIZE)
|
||||
NDMA_fill((u32*)(ROM_LOC + romSize), 0xFFFFFFFFu, MAX_ROM_SIZE - romSize);
|
||||
} while(0);
|
||||
|
||||
// Should we unmount on non-mount error here?
|
||||
else NDMA_fill((u32*)SAVE_LOC, 0xFFFFFFFFu, g_saveSize);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -197,7 +140,6 @@ Result LGY_getGbaRtc(GbaRtc *const out)
|
|||
Result LGY_backupGbaSave(void)
|
||||
{
|
||||
Result res = RES_OK;
|
||||
|
||||
if(g_saveSize != 0)
|
||||
{
|
||||
// Enable savegame mem region.
|
||||
|
@ -205,25 +147,19 @@ Result LGY_backupGbaSave(void)
|
|||
|
||||
u32 newHash[8];
|
||||
sha((u32*)SAVE_LOC, g_saveSize, newHash, SHA_INPUT_BIG | SHA_MODE_256, SHA_OUTPUT_BIG);
|
||||
if(memcmp(g_saveHash, newHash, 32) != 0) // Backup save if it changed.
|
||||
if(memcmp(g_saveHash, newHash, 32) != 0) // Backup save if changed.
|
||||
{
|
||||
FIL f;
|
||||
if(f_open(&f, "sdmc:/rom.sav", FA_OPEN_ALWAYS | FA_WRITE) == FR_OK)
|
||||
FHandle f;
|
||||
if((res = fOpen(&f, g_savePath, FA_OPEN_ALWAYS | FA_WRITE)) == RES_OK)
|
||||
{
|
||||
UINT written;
|
||||
if(f_write(&f, (void*)SAVE_LOC, g_saveSize, &written) != FR_OK)
|
||||
res = RES_FILE_WRITE_ERR;
|
||||
|
||||
f_close(&f);
|
||||
res = fWrite(f, (void*)SAVE_LOC, g_saveSize, NULL);
|
||||
fClose(f);
|
||||
}
|
||||
else res = RES_FILE_OPEN_ERR;
|
||||
}
|
||||
|
||||
// Disable savegame mem region.
|
||||
REG_LGY_GBA_SAVE_MAP = LGY_SAVE_MAP_7;
|
||||
}
|
||||
|
||||
f_mount(NULL, "sdmc:", 0);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "types.h"
|
||||
#include "ipc_handler.h"
|
||||
#include "hardware/cache.h"
|
||||
#include "fs.h"
|
||||
#include "hardware/lgy.h"
|
||||
#include "arm9/debug.h"
|
||||
|
||||
|
@ -35,8 +36,65 @@ u32 IPC_handleCmd(u8 cmdId, u32 inBufs, u32 outBufs, const u32 *const buf)
|
|||
u32 result = 0;
|
||||
switch(cmdId)
|
||||
{
|
||||
// Filesystem API.
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FMOUNT):
|
||||
result = fMount(buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FUNMOUNT):
|
||||
result = fUnmount(buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FGETFREE):
|
||||
result = fGetFree(buf[2], (u64*)buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FOPEN):
|
||||
result = fOpen((FHandle*)buf[2], (const char *const)buf[0], buf[4]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FREAD):
|
||||
result = fRead(buf[4], (void *const)buf[0], buf[1], (u32 *const)buf[2]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FWRITE):
|
||||
result = fWrite(buf[4], (const void *const)buf[0], buf[1], (u32 *const)buf[2]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FSYNC):
|
||||
result = fSync(buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FLSEEK):
|
||||
result = fLseek(buf[0], buf[1]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FTELL):
|
||||
result = fTell(buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FSIZE):
|
||||
result = fSize(buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FCLOSE):
|
||||
result = fClose(buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FSTAT):
|
||||
result = fStat((const char *const)buf[0], (FILINFO *const)buf[2]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FOPEN_DIR):
|
||||
result = fOpenDir((DHandle *const)buf[2], (const char *const)buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FREAD_DIR):
|
||||
result = fReadDir(buf[4], (FILINFO *const)buf[0], buf[5], (u32 *const)buf[2]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FCLOSE_DIR):
|
||||
result = fCloseDir(buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FMKDIR):
|
||||
result = fMkdir((const char *const)buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FRENAME):
|
||||
result = fRename((const char *const)buf[0], (const char *const)buf[2]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_FUNLINK):
|
||||
result = fUnlink((const char *const)buf[0]);
|
||||
break;
|
||||
|
||||
// open_agb_firm specific API.
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_PREPARE_GBA):
|
||||
result = LGY_prepareGbaMode(buf[0], buf[1]);
|
||||
result = LGY_prepareGbaMode(buf[2], buf[3], (const char *const)buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_SET_GBA_RTC):
|
||||
result = LGY_setGbaRtc(*((GbaRtc*)buf));
|
||||
|
@ -44,9 +102,13 @@ u32 IPC_handleCmd(u8 cmdId, u32 inBufs, u32 outBufs, const u32 *const buf)
|
|||
case IPC_CMD_ID_MASK(IPC_CMD9_GET_GBA_RTC):
|
||||
result = LGY_getGbaRtc((GbaRtc*)buf[0]);
|
||||
break;
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_PREPARE_POWER):
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_BACKUP_GBA_SAVE):
|
||||
result = LGY_backupGbaSave();
|
||||
break;
|
||||
|
||||
// Miscellaneous API.
|
||||
case IPC_CMD_ID_MASK(IPC_CMD9_PREPARE_POWER):
|
||||
break;
|
||||
default:
|
||||
panic();
|
||||
}
|
||||
|
|
|
@ -15,10 +15,28 @@ void printError(Result res)
|
|||
{
|
||||
"OK",
|
||||
"SD card removed",
|
||||
"Failed to mount drive",
|
||||
"Failed to open file",
|
||||
"Failed to read file",
|
||||
"Failed to write file"
|
||||
"Invalid argument",
|
||||
|
||||
// fatfs errors.
|
||||
"fatfs disk error",
|
||||
"fatfs assertion failed",
|
||||
"fatfs disk not ready",
|
||||
"fatfs file not found",
|
||||
"fatfs path not found",
|
||||
"fatfs invalid path name",
|
||||
"fatfs access denied",
|
||||
"fatfs already exists",
|
||||
"fatfs invalid file/directory object",
|
||||
"fatfs drive write protected",
|
||||
"fatfs invalid drive",
|
||||
"fatfs drive not mounted",
|
||||
"fatfs no filesystem",
|
||||
"fatfs f_mkfs() aborted",
|
||||
"fatfs thread lock timeout",
|
||||
"fatfs file locked",
|
||||
"fatfs not enough memory",
|
||||
"fatfs too many open objects",
|
||||
"fatfs invalid parameter"
|
||||
};
|
||||
static const char *const custom[] =
|
||||
{
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* This file is part of fastboot 3DS
|
||||
* Copyright (C) 2020 derrek, profi200
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "types.h"
|
||||
#include "fsutil.h"
|
||||
#include "fs.h"
|
||||
|
||||
|
||||
#define BLOCK_SIZE (1024u * 1024)
|
||||
|
||||
|
||||
|
||||
Result fsQuickRead(const char *const path, u32 off, void *buf, u32 size)
|
||||
{
|
||||
Result res;
|
||||
FHandle f;
|
||||
if((res = fOpen(&f, path, FA_OPEN_EXISTING | FA_READ)) == RES_OK)
|
||||
{
|
||||
if((res = fLseek(f, off)) == RES_OK)
|
||||
{
|
||||
u32 totalRead = 0;
|
||||
u32 read;
|
||||
while(size > totalRead && (res = fRead(f, buf, BLOCK_SIZE, &read)) == RES_OK && read == BLOCK_SIZE)
|
||||
{
|
||||
buf += read;
|
||||
totalRead += read;
|
||||
}
|
||||
}
|
||||
|
||||
fClose(f);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
|
@ -114,6 +114,10 @@ u32 PXI_sendCmd(u32 cmd, const u32 *buf, u32 words)
|
|||
const IpcBuffer *const inBuf = (IpcBuffer*)&buf[i * sizeof(IpcBuffer) / 4];
|
||||
if(inBuf->ptr && inBuf->size) cleanDCacheRange(inBuf->ptr, inBuf->size);
|
||||
}
|
||||
// Edge case:
|
||||
// memset() 256 bytes string buffer, fRead() 256 bytes from 10 bytes file and fWrite() them to another
|
||||
// file. The buffer will be filled with garbage where it wasn't overwritten because of the invalidate.
|
||||
// TODO: Should we flush here instead?
|
||||
for(u32 i = inBufs; i < inBufs + outBufs; i++)
|
||||
{
|
||||
const IpcBuffer *const outBuf = (IpcBuffer*)&buf[i * sizeof(IpcBuffer) / 4];
|
||||
|
@ -132,7 +136,7 @@ u32 PXI_sendCmd(u32 cmd, const u32 *buf, u32 words)
|
|||
|
||||
#ifdef ARM11
|
||||
// The CPU may do speculative prefetches of data after the first invalidation
|
||||
// so we need to do it again. Not sure if this is a ARMv6+ thing.
|
||||
// so we need to do it again.
|
||||
for(u32 i = inBufs; i < inBufs + outBufs; i++)
|
||||
{
|
||||
const IpcBuffer *const outBuf = (IpcBuffer*)&buf[i * sizeof(IpcBuffer) / 4];
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
/ and optional writing functions as well. */
|
||||
|
||||
|
||||
#define FF_FS_MINIMIZE 1
|
||||
#define FF_FS_MINIMIZE 0
|
||||
/* This option defines minimization level to remove some basic API functions.
|
||||
/
|
||||
/ 0: Basic functions are fully enabled.
|
||||
|
|
Loading…
Reference in New Issue