mirror of
https://github.com/Polprzewodnikowy/SummerCart64.git
synced 2025-04-08 15:51:15 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
88d654129a | ||
![]() |
b88c9a314b | ||
![]() |
e4c3f34fb0 | ||
![]() |
b520f9ace8 | ||
![]() |
d8c4f979cc | ||
![]() |
d63f5893da | ||
![]() |
d307e1a5b1 | ||
![]() |
65f8fa3cf7 | ||
![]() |
6c566bd530 | ||
![]() |
99060bec15 | ||
![]() |
63feaa0c2e | ||
![]() |
a59ad1d39b | ||
![]() |
30fb3d0ea6 | ||
![]() |
a3d4082384 | ||
![]() |
0739ca624c | ||
![]() |
bb1ce45dfe | ||
![]() |
9193e9c6f2 | ||
![]() |
3fbb6f3823 | ||
![]() |
f546e5d17d | ||
![]() |
8393963650 | ||
![]() |
20a9ec0087 | ||
![]() |
b3d9e98e68 | ||
![]() |
6698550dbd | ||
![]() |
18041e2547 | ||
![]() |
0538a28f9e | ||
![]() |
1ade3ade8e | ||
![]() |
80b4aa95cd | ||
![]() |
6eef811cd6 | ||
![]() |
e2c100ae7f | ||
![]() |
a6e86587ae | ||
![]() |
93ab101be4 | ||
![]() |
cc41652e6f | ||
![]() |
ed63eb3e8c | ||
![]() |
db4f16754f | ||
![]() |
74e20cb8cc |
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@ -1,2 +1 @@
|
||||
github: polprzewodnikowy
|
||||
ko_fi: polprzewodnikowy
|
||||
|
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
27
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,27 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
4.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Possible solution**
|
||||
Not obligatory, but suggest a fix/reason for the bug.
|
58
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[SC64][BUG] "
|
||||
labels: ["bug"]
|
||||
assignees:
|
||||
- Polprzewodnikowy
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: checkboxes
|
||||
id: sanity-check
|
||||
attributes:
|
||||
label: Is your issue really a bug?
|
||||
description: |
|
||||
Issue tracker in this repository is for **BUG REPORTS ONLY**.
|
||||
|
||||
Make sure your problem is caused by the firmware/PC app and **not** by the software you're running on the flashcart.
|
||||
|
||||
Errors in the documentation are also considered a bug.
|
||||
|
||||
If your issue is related to the menu then report it in the [N64FlashcartMenu] repository.
|
||||
|
||||
[N64FlashcartMenu]: https://github.com/Polprzewodnikowy/N64FlashcartMenu
|
||||
options:
|
||||
- label: I understand the difference between flashcart firmware, N64FlashcartMenu and `sc64deployer` PC app.
|
||||
required: true
|
||||
- label: I found a bug in FPGA HDL (`/fw/rtl`)
|
||||
- label: I found a bug in MCU app (`/sw/controller`)
|
||||
- label: I found a bug in N64 bootloader (`/sw/bootloader`)
|
||||
- label: I found a bug in PC app (`/sw/deployer`)
|
||||
- label: I found a bug in initial programming script (`/sw/tools/primer.py`)
|
||||
- label: I found an error in documentation (`/docs`)
|
||||
- label: I found an issue elsewhere
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Firmware version
|
||||
placeholder: v2.20.2
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: |
|
||||
Tell us what you noticed as a bug, and what is your expected outcome.
|
||||
The more detailed the description is the better.
|
||||
If applicable please attach screenshots and/or video showing the problem.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: deployer-info
|
||||
attributes:
|
||||
label: Output logs from `sc64deployer info`
|
||||
description: If possible, please copy and paste the output from the command specified above.
|
||||
render: shell
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
5
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,8 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: GitHub Discussions
|
||||
url: https://github.com/Polprzewodnikowy/SummerCart64/discussions
|
||||
about: Please use for QUESTIONS, conversations or discussions.
|
||||
- name: N64brew Discord
|
||||
url: https://discord.gg/8VNMKhxqQn
|
||||
about: Alternative channel for asking QUESTIONS.
|
||||
about: The go-to community to ask about SummerCart64
|
||||
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
20
.github/PULL_REQUEST_TEMPLATE/default.md
vendored
@ -1,20 +0,0 @@
|
||||
**Description**
|
||||
Provide a headline summary of your changes in the Title above.
|
||||
Describe your changes in detail.
|
||||
|
||||
**Related Issue**
|
||||
This project only accepts pull requests related to open issues.
|
||||
If suggesting a change, please discuss it in an issue first.
|
||||
If fixing a bug, there should be an issue describing it with steps to reproduce.
|
||||
Please link to the issue here:
|
||||
|
||||
**Motivation and Context**
|
||||
Why is this change required? What problem does it solve?
|
||||
If it fixes an open issue, please link to the issue here.
|
||||
|
||||
**How Has This Been Tested?**
|
||||
Please describe in detail how you tested your changes.
|
||||
Include details of your testing environment, and the tests you ran to see how your change affects other areas of the code, etc.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help demonstrate your feature/bugfix.
|
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -19,6 +19,8 @@ jobs:
|
||||
steps:
|
||||
- name: Download SummerCart64 repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set package version
|
||||
uses: frabert/replace-string-action@v2
|
||||
@ -28,9 +30,15 @@ jobs:
|
||||
string: '${{ github.ref_name }}'
|
||||
replace-with: '-'
|
||||
|
||||
- name: Retrieve the Lattice Diamond license from secrets and decode it
|
||||
run: echo $LICENSE | base64 --decode > fw/project/lcmxo2/license.dat
|
||||
env:
|
||||
LICENSE: ${{ secrets.LATTICE_DIAMOND_LICENSE_BASE64 }}
|
||||
|
||||
- name: Build firmware
|
||||
run: ./docker_build.sh release --force-clean
|
||||
env:
|
||||
MAC_ADDRESS: ${{ secrets.LATTICE_DIAMOND_MAC_ADDRESS }}
|
||||
SC64_VERSION: ${{ steps.version.outputs.replaced }}
|
||||
|
||||
- name: Upload artifact
|
||||
|
65
README.md
65
README.md
@ -4,24 +4,40 @@
|
||||
**For non-technical description of the SummerCart64, please head to the https://summercart64.dev website!**
|
||||
|
||||
## Features
|
||||
- 64 MiB SDRAM memory for game and save data
|
||||
- 16 MiB FLASH memory for bootloader and extended game data
|
||||
- 8 kiB on-chip buffer for general use
|
||||
- ~23.8 MiB/s peak transfer rate USB interface for data upload/download and debug functionality
|
||||
- ~23.8 MiB/s peak transfer rate SD card interface
|
||||
- EEPROM, SRAM and FlashRAM save types with automatic writeback to SD card
|
||||
- Battery backed real time clock (RTC)
|
||||
- Status LED and button for general use
|
||||
- 64DD add-on emulation
|
||||
- IS-Viewer 64 debug interface
|
||||
- N64 bootloader with support for IPL3 registers spoofing and loading menu from SD card
|
||||
- Dedicated open source menu written specifically for this flashcart - [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu)
|
||||
- Enhanced [UltraCIC_C](https://github.com/jago85/UltraCIC_C) emulation with automatic region switching and programmable seed/checksum values
|
||||
- PC app for communicating with flashcart (game/save data upload/download, feature enable control and debug terminal)
|
||||
- [UNFLoader](https://github.com/buu342/N64-UNFLoader) support
|
||||
- Initial programming via UART header or dedicated JTAG/SWD interfaces
|
||||
- Software and firmware updatable via USB interface
|
||||
- 3D printable plastic shell
|
||||
|
||||
- **ROM and Save Memory On-board**
|
||||
- 64 MiB SDRAM memory for game and save data (enough memory to support every retail game without compromise)
|
||||
- 16 MiB FLASH memory for bootloader and extended game data (with extended memory flashcart supports game ROMs up to 78 MiB)
|
||||
|
||||
- **Game Saves**
|
||||
- EEPROM 4k/16k, SRAM and FlashRAM save types with an automatic writeback to the SD card (no reset button press required)
|
||||
|
||||
- **Hardware-Dependent Game Features**
|
||||
- 64DD add-on emulation
|
||||
- Battery backed real time clock (RTC)
|
||||
|
||||
- **Menu**
|
||||
- Dedicated open source menu written specifically for this flashcart - [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu)
|
||||
|
||||
- **Game Development**
|
||||
- ~23.8 MiB/s peak transfer rate SD card interface
|
||||
- ~23.8 MiB/s peak transfer rate USB interface for data upload/download and debug functionality
|
||||
- PC app to access the flashcart features:
|
||||
- Game/save data upload/download
|
||||
- Feature enable control
|
||||
- Debug terminal
|
||||
- Access to the SD card
|
||||
- Firmware update
|
||||
- [UNFLoader](https://github.com/buu342/N64-UNFLoader) support
|
||||
- IS-Viewer 64 debug interface (fixed 64 kiB buffer with a movable base address)
|
||||
- 8 kiB on-chip buffer for general use
|
||||
- Status LED and button for general use
|
||||
- [UltraCIC_C](https://github.com/jago85/UltraCIC_C) emulation with automatic region switching and programmable seed/checksum values
|
||||
- N64 bootloader with support for IPL3 registers spoofing and loading menu from SD card
|
||||
|
||||
- **Cartridge Production**
|
||||
- Initial programming via UART header or via dedicated JTAG/SWD interfaces
|
||||
- 3D printable shell
|
||||
|
||||
---
|
||||
|
||||
@ -37,17 +53,6 @@
|
||||
|
||||
---
|
||||
|
||||
## Help / Q&A
|
||||
|
||||
For any questions related to this project, please use [*Discussions*](https://github.com/Polprzewodnikowy/SummerCart64/discussions) tab in GitHub repository.
|
||||
Using discussions tab is highly encouraged as it allows to have centralized knowledge database accessible for everyone interested in this project.
|
||||
|
||||
I'm also active at [N64brew](https://discord.gg/8VNMKhxqQn) Discord server as `korgeaux` but keep in mind that [*Discussions*](https://github.com/Polprzewodnikowy/SummerCart64/discussions) tab is a preferred option.
|
||||
|
||||
Note that my time is limited so I can't answer all questions.
|
||||
|
||||
---
|
||||
|
||||
## How do I get one?
|
||||
|
||||
Most up to date information about purchasing/manufacturing options is available on https://summercart64.dev website!
|
||||
@ -63,7 +68,7 @@ If you have even slightest doubt about the ordering or programming process, it i
|
||||
|
||||
**Full disclosure**: for every order made through [this link](https://www.pcbway.com/project/member/shareproject/?bmbno=1046ED64-8AEE-44) I will receive 10% of PCB manufacturing and PCB assembly service cost (price of the components is not included in the split). This is a great way of supporting further project development.
|
||||
|
||||
If you don't need a physical product but still want to support me then check my [GitHub sponsors](https://github.com/sponsors/Polprzewodnikowy) page.
|
||||
If you don't need a physical product but still want to support me then check the sponsor links on the [official website](https://summercart64.dev).
|
||||
|
||||
---
|
||||
|
||||
|
30
build.sh
30
build.sh
@ -17,6 +17,7 @@ TOP_FILES=(
|
||||
FILES=(
|
||||
"./assets/*"
|
||||
"./docs/*"
|
||||
"./hw/pcb/LICENSE"
|
||||
"./hw/pcb/sc64v2_bom.html"
|
||||
"./hw/pcb/sc64v2.kicad_pcb"
|
||||
"./hw/pcb/sc64v2.kicad_pro"
|
||||
@ -27,6 +28,8 @@ FILES=(
|
||||
"./README.md"
|
||||
)
|
||||
|
||||
HAVE_COMMIT_INFO=false
|
||||
|
||||
BUILT_BOOTLOADER=false
|
||||
BUILT_CONTROLLER=false
|
||||
BUILT_CIC=false
|
||||
@ -36,9 +39,24 @@ BUILT_RELEASE=false
|
||||
|
||||
FORCE_CLEAN=false
|
||||
|
||||
get_last_commit_info () {
|
||||
if [ "$HAVE_COMMIT_INFO" = true ]; then return; fi
|
||||
|
||||
SAFE_DIRECTORY="-c safe.directory=$(pwd)"
|
||||
|
||||
GIT_BRANCH=$(git $SAFE_DIRECTORY rev-parse --abbrev-ref HEAD)
|
||||
GIT_TAG=$(git $SAFE_DIRECTORY describe --tags 2> /dev/null)
|
||||
GIT_SHA=$(git $SAFE_DIRECTORY rev-parse HEAD)
|
||||
GIT_MESSAGE=$(git $SAFE_DIRECTORY log --oneline --format=%B -n 1 HEAD | head -n 1)
|
||||
|
||||
HAVE_COMMIT_INFO=true
|
||||
}
|
||||
|
||||
build_bootloader () {
|
||||
if [ "$BUILT_BOOTLOADER" = true ]; then return; fi
|
||||
|
||||
get_last_commit_info
|
||||
|
||||
pushd sw/bootloader > /dev/null
|
||||
if [ "$FORCE_CLEAN" = true ]; then
|
||||
make clean
|
||||
@ -98,6 +116,8 @@ build_fpga () {
|
||||
build_update () {
|
||||
if [ "$BUILT_UPDATE" = true ]; then return; fi
|
||||
|
||||
get_last_commit_info
|
||||
|
||||
build_bootloader
|
||||
build_controller
|
||||
build_cic
|
||||
@ -163,6 +183,14 @@ if test $# -eq 0; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
print_time () {
|
||||
echo "Build took $SECONDS seconds"
|
||||
}
|
||||
|
||||
trap "echo \"Build failed\"; print_time" ERR
|
||||
|
||||
SECONDS=0
|
||||
|
||||
TRIGGER_BOOTLOADER=false
|
||||
TRIGGER_CONTROLLER=false
|
||||
TRIGGER_CIC=false
|
||||
@ -213,3 +241,5 @@ if [ "$TRIGGER_CIC" = true ]; then build_cic; fi
|
||||
if [ "$TRIGGER_FPGA" = true ]; then build_fpga; fi
|
||||
if [ "$TRIGGER_UPDATE" = true ]; then build_update; fi
|
||||
if [ "$TRIGGER_RELEASE" = true ]; then build_release; fi
|
||||
|
||||
print_time
|
||||
|
@ -1,20 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
BUILDER_IMAGE="ghcr.io/polprzewodnikowy/sc64env:v1.10"
|
||||
BUILDER_PLATFORM="linux/x86_64"
|
||||
|
||||
pushd $(dirname $0) > /dev/null
|
||||
|
||||
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
||||
GIT_TAG=$(git describe --tags 2> /dev/null)
|
||||
GIT_SHA=$(git rev-parse HEAD)
|
||||
GIT_MESSAGE=$(git log --oneline --format=%B -n 1 HEAD | head -n 1)
|
||||
|
||||
if [ -t 1 ]; then
|
||||
DOCKER_OPTIONS="-it"
|
||||
fi
|
||||
|
||||
SECONDS=0
|
||||
|
||||
docker run \
|
||||
$DOCKER_OPTIONS \
|
||||
--rm \
|
||||
@ -23,20 +17,13 @@ docker run \
|
||||
-v "$(pwd)"/fw/project/lcmxo2/license.dat:/flexlm/license.dat \
|
||||
-v "$(pwd)":/workdir \
|
||||
-h=`hostname` \
|
||||
-e GIT_BRANCH="$GIT_BRANCH" \
|
||||
-e GIT_TAG="$GIT_TAG" \
|
||||
-e GIT_SHA="$GIT_SHA" \
|
||||
-e GIT_MESSAGE="$GIT_MESSAGE" \
|
||||
-e SC64_VERSION=${SC64_VERSION:-""} \
|
||||
--platform $BUILDER_PLATFORM \
|
||||
$BUILDER_IMAGE \
|
||||
./build.sh $@
|
||||
|
||||
BUILD_ERROR=$?
|
||||
|
||||
echo "Build took $SECONDS seconds"
|
||||
BUILD_RESULT=$?
|
||||
|
||||
popd > /dev/null
|
||||
|
||||
if [ $BUILD_ERROR -ne 0 ]; then
|
||||
exit -1
|
||||
fi
|
||||
exit $BUILD_RESULT
|
||||
|
@ -1,50 +1,38 @@
|
||||
- [First time setup on PC](#first-time-setup-on-pc)
|
||||
- [Firmware backup/update](#firmware-backupupdate)
|
||||
- [Running menu in standalone mode](#running-menu-in-standalone-mode)
|
||||
- [Uploading game and/or save from PC](#uploading-game-andor-save-from-pc)
|
||||
- [Downloading save to PC](#downloading-save-to-pc)
|
||||
- [Running 64DD games from PC](#running-64dd-games-from-pc)
|
||||
- [Direct boot option](#direct-boot-option)
|
||||
- [Debug terminal on PC](#debug-terminal-on-pc)
|
||||
- [First time setup](#first-time-setup)
|
||||
- [Standalone mode (Running menu and games on the N64)](#standalone-mode-running-menu-and-games-on-the-n64)
|
||||
- [Developer mode (Uploading ROMs from the PC, and more)](#developer-mode-uploading-roms-from-the-pc-and-more)
|
||||
- [Uploading game and/or save from PC](#uploading-game-andor-save-from-pc)
|
||||
- [Downloading save to PC](#downloading-save-to-pc)
|
||||
- [Running 64DD games from PC](#running-64dd-games-from-pc)
|
||||
- [Direct boot option](#direct-boot-option)
|
||||
- [Debug terminal on PC](#debug-terminal-on-pc)
|
||||
- [Firmware backup/update](#firmware-backupupdate)
|
||||
- [LED blink patters](#led-blink-patters)
|
||||
|
||||
---
|
||||
|
||||
## First time setup on PC
|
||||
# First time setup
|
||||
|
||||
**Windows platform: replace `./sc64deployer` in examples below with `sc64deployer.exe`**
|
||||
|
||||
1. Download the latest deployer tool (`sc64-deployer-{os}-{version}.{ext}`) and firmware (`sc64-firmware-{version}.bin`) from GitHub releases page
|
||||
2. Extract deployer tool package contents to a folder and place firmware file inside it
|
||||
3. Connect SC64 device to your computer with USB type C cable
|
||||
4. Run `./sc64deployer list` to check if device is detected in the system
|
||||
5. Update SC64 firmware to the latest version with `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
||||
6. Run `./sc64deployer info` to check if update process finished successfully and SC64 is detected correctly
|
||||
|
||||
---
|
||||
|
||||
## Firmware backup/update
|
||||
|
||||
Keeping SC64 firmware up to date is highly recommended.
|
||||
`sc64deployer` application is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
||||
|
||||
To download and backup current version of the SC64 firmware run `./sc64deployer firmware backup sc64-firmware-backup.bin`
|
||||
|
||||
To update SC64 firmware run `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
||||
|
||||
To print firmware metadata run `./sc64deployer firmware info sc64-firmware-{version}.bin`
|
||||
|
||||
---
|
||||
|
||||
## Running menu in standalone mode
|
||||
## Standalone mode (Running menu and games on the N64)
|
||||
|
||||
Menu, as known from 64drive or EverDrive-64, is developed in another repository: [N64FlashcartMenu](https://github.com/Polprzewodnikowy/N64FlashcartMenu).
|
||||
Download latest version from [here](https://github.com/Polprzewodnikowy/N64FlashcartMenu/releases) and put `sc64menu.n64` file in the root directory of the SD card.
|
||||
Additionally, follow the instructions in the N64FlashcartMenu repository for more information about thr SD card setup and extra functionality.
|
||||
When N64 is powered on menu is automatically loaded from the SD card. Supported file system formats are FAT32 and exFAT.
|
||||
|
||||
---
|
||||
|
||||
## Uploading game and/or save from PC
|
||||
## Developer mode (Uploading ROMs from the PC, and more)
|
||||
|
||||
**Windows platform: replace `./sc64deployer` in examples below with `sc64deployer.exe`**
|
||||
|
||||
1. Download the latest deployer tool (`sc64-deployer-{os}-{version}.{ext}`) from the GitHub releases page
|
||||
2. Extract deployer tool package contents to a folder
|
||||
3. Connect SC64 device to your computer with USB type C cable
|
||||
4. Run `./sc64deployer list` to check if device is detected in the system
|
||||
5. Follow instructions below for specific use cases
|
||||
|
||||
### Uploading game and/or save from PC
|
||||
|
||||
`./sc64deployer upload path_to_rom.n64 --save-type eeprom4k --save path_to_save.sav`
|
||||
|
||||
@ -53,18 +41,14 @@ Application will try to autodetect used save type so explicitly setting save typ
|
||||
Check included help in the application to list available save types.
|
||||
Arguments `--save-type` and/or `--save` can be omitted if game doesn't require any save or you want to start with fresh save file.
|
||||
|
||||
---
|
||||
|
||||
## Downloading save to PC
|
||||
### Downloading save to PC
|
||||
|
||||
`./sc64deployer download save path_to_save.sav`
|
||||
|
||||
Replace `path_to_save.sav` with appropriate value.
|
||||
Command will raise error when no save type is currently enabled in the SC64 device.
|
||||
|
||||
---
|
||||
|
||||
## Running 64DD games from PC
|
||||
### Running 64DD games from PC
|
||||
|
||||
64DD games require DDIPL ROM and disk images.
|
||||
To run disk game type `./sc64deployer 64dd path_to_ddipl.n64 path_to_disk_1.ndd path_to_disk_2.ndd`.
|
||||
@ -79,31 +63,38 @@ Make sure retail and development disks formats aren't mixed together.
|
||||
If disk game supports running in conjunction with cartridge game then `--rom path_to_rom.n64` argument can be added to command above.
|
||||
N64 will boot cartridge game instead of 64DD IPL.
|
||||
|
||||
---
|
||||
|
||||
## Direct boot option
|
||||
### Direct boot option
|
||||
|
||||
If booting game through included bootloader isn't a desired option then flashcart can be put in special mode that omits this step.
|
||||
Pass `--direct` option in `upload` or `64dd` command to disable bootloader during boot and console reset.
|
||||
This option is useful only for very specific cases (e.g. testing custom IPL3 or running SC64 on top of GameShark).
|
||||
TV type cannot be forced when direct boot mode is enabled.
|
||||
|
||||
---
|
||||
|
||||
## Debug terminal on PC
|
||||
### Debug terminal on PC
|
||||
|
||||
`sc64deployer` application supports UNFLoader protocol and has same functionality implemented as aforementioned program.
|
||||
Type `./sc64deployer debug` to activate it.
|
||||
|
||||
### Firmware backup/update
|
||||
|
||||
Keeping SC64 firmware up to date is strongly recommended.
|
||||
`sc64deployer` application is tightly coupled with specific firmware versions and will error out when it detects unsupported firmware version.
|
||||
|
||||
To download and backup current version of the SC64 firmware run `./sc64deployer firmware backup sc64-firmware-backup.bin`
|
||||
|
||||
To update SC64 firmware run `./sc64deployer firmware update sc64-firmware-{version}.bin`
|
||||
|
||||
To print firmware metadata run `./sc64deployer firmware info sc64-firmware-{version}.bin`
|
||||
|
||||
---
|
||||
|
||||
## LED blink patters
|
||||
# LED blink patters
|
||||
|
||||
LED on SC64 board can blink in certain situations. Most of them during normal use are related to SD card access. Here's list of blink patterns:
|
||||
|
||||
| Pattern | Meaning |
|
||||
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| Nx [Short ON - Short OFF] | SD card access is in progress (initialization or data read/write) or save writeback is in progress |
|
||||
| Irregular | SD card access is in progress (initialization or data read/write) or save writeback was finished |
|
||||
| Nx [Medium ON - Long OFF] | CIC region did not match, please power off console and power on again |
|
||||
| 2x [Very short ON - Short OFF] | Pattern used during firmware update process, it means that specific part of firmware has started programming |
|
||||
| 10x [Very short ON - Very short OFF] | Firmware has been successfully updated |
|
||||
@ -112,4 +103,4 @@ LED on SC64 board can blink in certain situations. Most of them during normal us
|
||||
Nx means that blink count is varied.
|
||||
|
||||
LED blinking on SD card access can be disabled through `sc64deployer` application.
|
||||
Please refer to included help for option to change the LED behavior.
|
||||
Please refer to the included help for option to change the LED behavior.
|
||||
|
@ -25,9 +25,9 @@ This mapping is used internally by FPGA/μC and when accessing flashcart from US
|
||||
| Flash [1] | `0x0400_0000` | 16 MiB | RW/R | Flash |
|
||||
| Data buffer | `0x0500_0000` | 8 kiB | RW | BlockRAM |
|
||||
| EEPROM | `0x0500_2000` | 2 kiB | RW | BlockRAM |
|
||||
| 64DD buffer | `0x0500_2800` | 256 bytes | RW | BlockRAM |
|
||||
| FlashRAM buffer [2] | `0x0500_2900` | 128 bytes | R | BlockRAM |
|
||||
| N/A [3] | `0x0500_2980` | to `0x07FF_FFFF` | R | N/A |
|
||||
| 64DD/MCU buffer | `0x0500_2800` | 1 kiB | RW | BlockRAM |
|
||||
| FlashRAM buffer [2] | `0x0500_2C00` | 128 bytes | R | BlockRAM |
|
||||
| N/A [3] | `0x0500_2C80` | to `0x07FF_FFFF` | R | N/A |
|
||||
|
||||
- Note [1]: Flash memory region `0x04E0_0000` - `0x04FD_FFFF` is write protected as it contains N64 bootloader. This section can be overwritten only via firmware update process.
|
||||
- Note [2]: Due to BlockRAM usage optimization this section is read only.
|
||||
@ -53,8 +53,8 @@ This mapping is used when accessing flashcart from N64 side.
|
||||
| ROM shadow [7] | `0x1FFC_0000` | 128 kiB | R | `0x04FE_0000` | Flash | mem bus | SC64 register access is enabled |
|
||||
| Data buffer | `0x1FFE_0000` | 8 kiB | RW | `0x0500_0000` | Block RAM | mem bus | SC64 register access is enabled |
|
||||
| EEPROM | `0x1FFE_2000` | 2 kiB | RW | `0x0500_2000` | Block RAM | mem bus | SC64 register access is enabled |
|
||||
| 64DD buffer [8] | `0x1FFE_2800` | 256 bytes | RW | `0x0500_2800` | Block RAM | mem bus | SC64 register access is enabled |
|
||||
| FlashRAM buffer [8] | `0x1FFE_2900` | 128 bytes | R | `0x0500_2900` | Block RAM | mem bus | SC64 register access is enabled |
|
||||
| 64DD/MCU buffer [8] | `0x1FFE_2800` | 1 kiB | RW | `0x0500_2800` | Block RAM | mem bus | SC64 register access is enabled |
|
||||
| FlashRAM buffer [8] | `0x1FFE_2C00` | 128 bytes | R | `0x0500_2C00` | Block RAM | mem bus | SC64 register access is enabled |
|
||||
| SC64 registers | `0x1FFF_0000` | 28 bytes | RW | N/A | Flashcart Interface | reg bus | SC64 register access is enabled |
|
||||
|
||||
- Note [1]: 64DD IPL share SDRAM memory space with ROM (last 4 MiB minus 128 kiB for saves). Write access is always disabled for this section.
|
||||
|
@ -4,28 +4,28 @@
|
||||
|
||||
## N64 commands
|
||||
|
||||
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
||||
| --- | --------------------- | ------------- | ------------ | ---------------- | -------------- | ---------------------------------------------------------- |
|
||||
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
||||
| `V` | **VERSION_GET** | --- | --- | major/minor | revision | Get flashcart firmware version |
|
||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||
| `C` | **CONFIG_SET** | config_id | new_value | --- | previous_value | Set config option and get previous value |
|
||||
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
||||
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
||||
| `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value |
|
||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||
| `m` | **USB_READ** | pi_address | length | --- | --- | Receive data from USB to flashcart |
|
||||
| `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB |
|
||||
| `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length |
|
||||
| `U` | **USB_WRITE_STATUS** | --- | --- | write_status | --- | Get USB write status |
|
||||
| `i` | **SD_CARD_OP** | pi_address | operation | --- | return_data | Perform special operation on SD card |
|
||||
| `I` | **SD_SECTOR_SET** | sector | --- | --- | --- | Set starting sector for next SD card R/W operation |
|
||||
| `s` | **SD_READ** | pi_address | sector_count | --- | --- | Read sectors from SD card to flashcart |
|
||||
| `S` | **SD_WRITE** | pi_address | sector_count | --- | --- | Write sectors from flashcart to SD card |
|
||||
| `D` | **DISK_MAPPING_SET** | pi_address | table_size | --- | --- | Set 64DD disk mapping for SD mode |
|
||||
| `w` | **WRITEBACK_PENDING** | --- | --- | pending_status | --- | Get save writeback status (is write queued to the SD card) |
|
||||
| `W` | **WRITEBACK_SD_INFO** | pi_address | --- | --- | --- | Load writeback SD sector table and enable it |
|
||||
| `K` | **FLASH_PROGRAM** | pi_address | length | --- | --- | Program flash with bytes loaded into data buffer |
|
||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | erase_block_size | --- | Wait until flash ready / get block erase size |
|
||||
| `P` | **FLASH_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
|
||||
| `%` | **DIAGNOSTIC_GET** | diagnostic_id | --- | --- | value | Get diagnostic data |
|
||||
| id | name | arg0 | arg1 | rsp0 | rsp1 | description |
|
||||
| --- | --------------------- | ------------- | ------------ | ---------------- | -------------- | ------------------------------------------------------------ |
|
||||
| `v` | **IDENTIFIER_GET** | --- | --- | identifier | --- | Get flashcart identifier `SCv2` |
|
||||
| `V` | **VERSION_GET** | --- | --- | major/minor | revision | Get flashcart firmware version |
|
||||
| `c` | **CONFIG_GET** | config_id | --- | --- | current_value | Get config option |
|
||||
| `C` | **CONFIG_SET** | config_id | new_value | --- | previous_value | Set config option and get previous value |
|
||||
| `a` | **SETTING_GET** | setting_id | --- | --- | current_value | Get persistent setting option |
|
||||
| `A` | **SETTING_SET** | setting_id | new_value | --- | --- | Set persistent setting option |
|
||||
| `t` | **TIME_GET** | --- | --- | time_0 | time_1 | Get current RTC value |
|
||||
| `T` | **TIME_SET** | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||
| `m` | **USB_READ** | pi_address | length | --- | --- | Receive data from USB to flashcart |
|
||||
| `M` | **USB_WRITE** | pi_address | length/type | --- | --- | Send data from from flashcart to USB |
|
||||
| `u` | **USB_READ_STATUS** | --- | --- | read_status/type | length | Get USB read status and type/length |
|
||||
| `U` | **USB_WRITE_STATUS** | --- | --- | write_status | --- | Get USB write status |
|
||||
| `i` | **SD_CARD_OP** | pi_address | operation | --- | return_data | Perform special operation on the SD card |
|
||||
| `I` | **SD_SECTOR_SET** | sector | --- | --- | --- | Set starting sector for next SD card R/W operation |
|
||||
| `s` | **SD_READ** | pi_address | sector_count | --- | --- | Read sectors from the SD card to flashcart memory space |
|
||||
| `S` | **SD_WRITE** | pi_address | sector_count | --- | --- | Write sectors from the flashcart memory space to the SD card |
|
||||
| `D` | **DISK_MAPPING_SET** | pi_address | table_size | --- | --- | Set 64DD disk mapping for SD mode |
|
||||
| `w` | **WRITEBACK_PENDING** | --- | --- | pending_status | --- | Get save writeback status (is write queued to the SD card) |
|
||||
| `W` | **WRITEBACK_SD_INFO** | pi_address | --- | --- | --- | Load writeback SD sector table and enable it |
|
||||
| `K` | **FLASH_PROGRAM** | pi_address | length | --- | --- | Program flash with bytes loaded into data buffer |
|
||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | erase_block_size | --- | Wait until flash ready / get block erase size |
|
||||
| `P` | **FLASH_ERASE_BLOCK** | pi_address | --- | --- | --- | Start flash block erase |
|
||||
| `%` | **DIAGNOSTIC_GET** | diagnostic_id | --- | --- | value | Get diagnostic data |
|
||||
|
@ -3,7 +3,7 @@
|
||||
- [PC -\> SC64 packets](#pc---sc64-packets)
|
||||
- [**`CMD`** packet](#cmd-packet)
|
||||
- [SC64 -\> PC packets](#sc64---pc-packets)
|
||||
- [**`RSP`/`ERR`** packets](#rsperr-packets)
|
||||
- [**`CMP`/`ERR`** packets](#cmperr-packets)
|
||||
- [**`PKT`** packet](#pkt-packet)
|
||||
- [Supported commands](#supported-commands)
|
||||
- [`v`: **IDENTIFIER\_GET**](#v-identifier_get)
|
||||
@ -45,6 +45,22 @@
|
||||
- [`data` (data)](#data-data-1)
|
||||
- [`X`: **AUX\_WRITE**](#x-aux_write)
|
||||
- [`arg0` (data)](#arg0-data)
|
||||
- [`i`: **SD\_CARD\_OP**](#i-sd_card_op)
|
||||
- [`arg0` (address)](#arg0-address-2)
|
||||
- [`arg1` (operation)](#arg1-operation)
|
||||
- [`response` (result/status)](#response-resultstatus)
|
||||
- [Available SD card operations](#available-sd-card-operations)
|
||||
- [SD card status](#sd-card-status)
|
||||
- [`s`: **SD\_READ**](#s-sd_read)
|
||||
- [`arg0` (address)](#arg0-address-3)
|
||||
- [`arg1` (sector\_count)](#arg1-sector_count)
|
||||
- [`data` (sector)](#data-sector)
|
||||
- [`response` (result)](#response-result)
|
||||
- [`S`: **SD\_WRITE**](#s-sd_write)
|
||||
- [`arg0` (address)](#arg0-address-4)
|
||||
- [`arg1` (sector\_count)](#arg1-sector_count-1)
|
||||
- [`data` (sector)](#data-sector-1)
|
||||
- [`response` (result)](#response-result-1)
|
||||
- [`D`: **DD\_SET\_BLOCK\_READY**](#d-dd_set_block_ready)
|
||||
- [`arg0` (error)](#arg0-error)
|
||||
- [`W`: **WRITEBACK\_ENABLE**](#w-writeback_enable)
|
||||
@ -112,29 +128,29 @@ Packet data length is derived from the argument if specific command supports it.
|
||||
|
||||
| identifier | description |
|
||||
| ---------- | ---------------------------------------- |
|
||||
| `RSP` | Success response to the received command |
|
||||
| `CMP` | Success response to the received command |
|
||||
| `ERR` | Error response to the received command |
|
||||
| `PKT` | Asynchronous data packet |
|
||||
|
||||
SC64 sends response packet `RSP`/`ERR` to almost every command received from the PC.
|
||||
SC64 sends response packet `CMP`/`ERR` to almost every command received from the PC.
|
||||
Fourth byte is the same as in the command that triggered the response.
|
||||
If command execution was not successful, then `RSP` identifier is replaced by the `ERR` identifier.
|
||||
If command execution was not successful, then `CMP` identifier is replaced by the `ERR` identifier.
|
||||
|
||||
SC64 can also send `PKT` packet at any time as a response to action triggered by the N64 or the flashcart itself.
|
||||
Fourth byte denotes packet ID listed in the [asynchronous packets](#asynchronous-packets) section.
|
||||
|
||||
#### **`RSP`/`ERR`** packets
|
||||
#### **`CMP`/`ERR`** packets
|
||||
|
||||
General structure of packet:
|
||||
| offset | type | description |
|
||||
| ------ | -------------------- | ---------------------- |
|
||||
| `0` | char[3] | `RSP`/`ERR` identifier |
|
||||
| `0` | char[3] | `CMP`/`ERR` identifier |
|
||||
| `3` | char[1] | Command ID |
|
||||
| `4` | uint32_t | Response data length |
|
||||
| `8` | uint8_t[data_length] | Response data (if any) |
|
||||
|
||||
`RSP`/`ERR` packet is sent as a response to the command sent by the PC.
|
||||
`ERR` response might contain no (or undefined) data in the arbitrary data field compared to regular `RSP` packet.
|
||||
`CMP`/`ERR` packet is sent as a response to the command sent by the PC.
|
||||
`ERR` response might contain no (or undefined) data in the arbitrary data field compared to regular `CMP` packet.
|
||||
|
||||
#### **`PKT`** packet
|
||||
|
||||
@ -152,30 +168,33 @@ Available packet IDs are listed in the [asynchronous packets](#asynchronous-pack
|
||||
|
||||
## Supported commands
|
||||
|
||||
| id | name | arg0 | arg1 | data | response | description |
|
||||
| --- | ----------------------------------------------- | ------------ | ------------- | ---- | ---------------- | -------------------------------------------------------------- |
|
||||
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
||||
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
|
||||
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
||||
| `B` | [**CIC_PARAMS_SET**](#b-cic_params_set) | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
|
||||
| `c` | [**CONFIG_GET**](#c-config_get) | config_id | --- | --- | config_value | Get config option |
|
||||
| `C` | [**CONFIG_SET**](#c-config_set) | config_id | config_value | --- | --- | Set config option |
|
||||
| `a` | [**SETTING_GET**](#a-setting_get) | setting_id | --- | --- | setting_value | Get persistent setting option |
|
||||
| `A` | [**SETTING_SET**](#a-setting_set) | setting_id | setting_value | --- | --- | Set persistent setting option |
|
||||
| `t` | [**TIME_GET**](#t-time_get) | --- | --- | --- | time | Get current RTC value |
|
||||
| `T` | [**TIME_SET**](#t-time_set) | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||
| `m` | [**MEMORY_READ**](#m-memory_read) | address | length | --- | data | Read data from specified memory address |
|
||||
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
|
||||
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
||||
| `X` | [**AUX_WRITE**](#x-aux_write) | data | --- | --- | --- | Send small auxiliary data to be received by app running on N64 |
|
||||
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
|
||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
||||
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
||||
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
||||
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
|
||||
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
|
||||
| `%` | **DIAGNOSTIC_GET** | --- | --- | --- | diagnostic_data | Get diagnostic data |
|
||||
| id | name | arg0 | arg1 | data | response | description |
|
||||
| --- | ----------------------------------------------- | ------------ | ------------- | ------ | ---------------- | -------------------------------------------------------------- |
|
||||
| `v` | [**IDENTIFIER_GET**](#v-identifier_get) | --- | --- | --- | identifier | Get flashcart identifier `SCv2` |
|
||||
| `V` | [**VERSION_GET**](#v-version_get) | --- | --- | --- | version | Get flashcart firmware version |
|
||||
| `R` | [**STATE_RESET**](#r-state_reset) | --- | --- | --- | --- | Reset flashcart state (CIC params and config options) |
|
||||
| `B` | [**CIC_PARAMS_SET**](#b-cic_params_set) | cic_params_0 | cic_params_1 | --- | --- | Set CIC emulation parameters (disable/seed/checksum) |
|
||||
| `c` | [**CONFIG_GET**](#c-config_get) | config_id | --- | --- | config_value | Get config option |
|
||||
| `C` | [**CONFIG_SET**](#c-config_set) | config_id | config_value | --- | --- | Set config option |
|
||||
| `a` | [**SETTING_GET**](#a-setting_get) | setting_id | --- | --- | setting_value | Get persistent setting option |
|
||||
| `A` | [**SETTING_SET**](#a-setting_set) | setting_id | setting_value | --- | --- | Set persistent setting option |
|
||||
| `t` | [**TIME_GET**](#t-time_get) | --- | --- | --- | time | Get current RTC value |
|
||||
| `T` | [**TIME_SET**](#t-time_set) | time_0 | time_1 | --- | --- | Set new RTC value |
|
||||
| `m` | [**MEMORY_READ**](#m-memory_read) | address | length | --- | data | Read data from specified memory address |
|
||||
| `M` | [**MEMORY_WRITE**](#m-memory_write) | address | length | data | --- | Write data to specified memory address |
|
||||
| `U` | [**USB_WRITE**](#u-usb_write) | type | length | data | N/A | Send data to be received by app running on N64 (no response!) |
|
||||
| `X` | [**AUX_WRITE**](#x-aux_write) | data | --- | --- | --- | Send small auxiliary data to be received by app running on N64 |
|
||||
| `i` | [**SD_CARD_OP**](#i-sd_card_op) | address | operation | --- | result/status | Perform special operation on the SD card |
|
||||
| `s` | [**SD_READ**](#s-sd_read) | address | sector_count | sector | result | Read sectors from the SD card to flashcart memory space |
|
||||
| `S` | [**SD_WRITE**](#s-sd_write) | address | sector_count | sector | result | Write sectors from the flashcart memory space to the SD card |
|
||||
| `D` | [**DD_SET_BLOCK_READY**](#d-dd_set_block_ready) | error | --- | --- | --- | Notify flashcart about 64DD block readiness |
|
||||
| `W` | [**WRITEBACK_ENABLE**](#w-writeback_enable) | --- | --- | --- | --- | Enable save writeback through USB packet |
|
||||
| `p` | **FLASH_WAIT_BUSY** | wait | --- | --- | erase_block_size | Wait until flash ready / Get flash block erase size |
|
||||
| `P` | **FLASH_ERASE_BLOCK** | address | --- | --- | --- | Start flash block erase |
|
||||
| `f` | **FIRMWARE_BACKUP** | address | --- | --- | status/length | Backup firmware to specified memory address |
|
||||
| `F` | **FIRMWARE_UPDATE** | address | length | --- | status | Update firmware from specified memory address |
|
||||
| `?` | **DEBUG_GET** | --- | --- | --- | debug_data | Get internal FPGA debug info |
|
||||
| `%` | **DIAGNOSTIC_GET** | --- | --- | --- | diagnostic_data | Get diagnostic data |
|
||||
|
||||
---
|
||||
|
||||
@ -223,7 +242,7 @@ _This command does not require arguments or data._
|
||||
_This command does not send response data._
|
||||
|
||||
This command is used to reset most of the config options to default state (same as on power-up).
|
||||
Additionally, CIC emulation is enabled and 6102/7101 seed/checksum values are set.
|
||||
Additionally, CIC emulation is enabled, 6102/7101 seed/checksum values are set and SD card lock is released.
|
||||
|
||||
---
|
||||
|
||||
@ -444,7 +463,7 @@ Writes bytes to the specified memory address. Please refer to the [internal memo
|
||||
|
||||
_This command does not send response data._
|
||||
|
||||
_This command does not send `RSP`/`ERR` packet response!_
|
||||
_This command does not send `CMP`/`ERR` packet response!_
|
||||
|
||||
This command notifies N64 that data is waiting to be acknowledged.
|
||||
If N64 side doesn't acknowledge data via [`m` **USB_READ**](./02_n64_commands.md#m-usb_read) N64 command within 1 second then data is flushed and [`G` **DATA_FLUSHED**](#asynchronous-packets) asynchronous packet is sent to the PC.
|
||||
@ -467,6 +486,110 @@ This command puts 32 bits of data to the AUX register accessible from the N64 si
|
||||
|
||||
---
|
||||
|
||||
### `i`: **SD_CARD_OP**
|
||||
|
||||
**Perform special operation on the SD card**
|
||||
|
||||
#### `arg0` (address)
|
||||
| bits | description |
|
||||
| -------- | ----------- |
|
||||
| `[31:0]` | Address |
|
||||
|
||||
#### `arg1` (operation)
|
||||
| bits | description |
|
||||
| -------- | ----------- |
|
||||
| `[31:0]` | Operation |
|
||||
|
||||
#### `response` (result/status)
|
||||
| offset | type | description |
|
||||
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||
| `4` | uint32_t | SD card status (always returned and valid regardless of the SD card operation result) |
|
||||
|
||||
This command performs special operation on the SD card. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||
PC and N64 cannot use the SD card interface at the same time. Lock mechanism is implemented to prevent data corruption.
|
||||
SD card access is locked when PC or N64 requests SD card initialization, and unlocked when the card is uninitialized.
|
||||
Lock is also released when certain conditions occur - when N64 side lock is active then it is released when the console is powered off
|
||||
and save writeback operation is not pending. PC side lock is released when USB interface is in reset state, or the USB cable is unplugged.
|
||||
|
||||
#### Available SD card operations
|
||||
| operation | description |
|
||||
| --------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `0` | Init SD card |
|
||||
| `1` | Deinit SD card |
|
||||
| `2` | Get SD card status |
|
||||
| `3` | Get SD card info (loads CSD and CID registers to a specified address, data length is 32 bytes) |
|
||||
| `4` | Turn on byte swap |
|
||||
| `5` | Turn off byte swap |
|
||||
|
||||
#### SD card status
|
||||
| bits | description |
|
||||
| -------- | -------------------------------------------------------------------------- |
|
||||
| `[31:5]` | _Unused_ |
|
||||
| `[4]` | `0` - Byte swap disabled, `1` - Byte swap enabled (valid when initialized) |
|
||||
| `[3]` | `0` - 25 MHz clock, `1` - 50 MHz clock (valid when initialized) |
|
||||
| `[2]` | `0` - Byte addressed, `1` - Sector addressed (valid when initialized) |
|
||||
| `[1]` | `0` - SD card not initialized, `1` - SD card initialized |
|
||||
| `[0]` | `0` - SD card not inserted, `1` - SD card inserted |
|
||||
|
||||
---
|
||||
|
||||
### `s`: **SD_READ**
|
||||
|
||||
**Read sectors from the SD card to flashcart memory space**
|
||||
|
||||
#### `arg0` (address)
|
||||
| bits | description |
|
||||
| -------- | ----------- |
|
||||
| `[31:0]` | Address |
|
||||
|
||||
#### `arg1` (sector_count)
|
||||
| bits | description |
|
||||
| -------- | ------------ |
|
||||
| `[31:0]` | Sector count |
|
||||
|
||||
#### `data` (sector)
|
||||
| offset | type | description |
|
||||
| ------ | -------- | --------------- |
|
||||
| `0` | uint32_t | Starting sector |
|
||||
|
||||
#### `response` (result)
|
||||
| offset | type | description |
|
||||
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||
|
||||
This command reads sectors from the SD card to a specified memory address. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||
|
||||
---
|
||||
|
||||
### `S`: **SD_WRITE**
|
||||
|
||||
**Write sectors from the flashcart memory space to the SD card**
|
||||
|
||||
#### `arg0` (address)
|
||||
| bits | description |
|
||||
| -------- | ----------- |
|
||||
| `[31:0]` | Address |
|
||||
|
||||
#### `arg1` (sector_count)
|
||||
| bits | description |
|
||||
| -------- | ------------ |
|
||||
| `[31:0]` | Sector count |
|
||||
|
||||
#### `data` (sector)
|
||||
| offset | type | description |
|
||||
| ------ | -------- | --------------- |
|
||||
| `0` | uint32_t | Starting sector |
|
||||
|
||||
#### `response` (result)
|
||||
| offset | type | description |
|
||||
| ------ | -------- | ---------------------------------------------------------------------------------------------- |
|
||||
| `0` | uint32_t | Operation result (valid values are listed in the [sd_error_t](../sw/controller/src/sd.h) enum) |
|
||||
|
||||
This command writes sectors from a specified memory address to the SD card. When operation result is not `SD_OK`, then `ERR` packet is returned.
|
||||
|
||||
---
|
||||
|
||||
### `D`: **DD_SET_BLOCK_READY**
|
||||
|
||||
**Notify flashcart about 64DD block readiness**
|
||||
|
@ -133,9 +133,10 @@ type: *enum* | default: `0`
|
||||
- `4` - FlashRAM 1 Mib save is enabled
|
||||
- `5` - SRAM 768 kib save is enabled
|
||||
- `6` - SRAM 1 Mib save is enabled
|
||||
- `7` - FakeFlashRAM 1 Mib save is enabled (write/erase timings are not emulated and erase before write is not required)
|
||||
|
||||
Use this setting for selecting save type that will be emulated. Only one save type can be enabled.
|
||||
Any successful write to this config will disable automatic save writeback to USB or SD card when previously enabled.
|
||||
Any successful write to this config will disable automatic save writeback to the USB or SD card if previously enabled.
|
||||
|
||||
---
|
||||
|
||||
|
@ -10,13 +10,14 @@
|
||||
|
||||
Docker method is a preferred option.
|
||||
Run `./docker_build.sh release` to build all firmware/software and generate release package.
|
||||
For other options run script without any command to print help.
|
||||
For other options run script without any command to print help about available options.
|
||||
|
||||
#### Lattice Diamond license
|
||||
|
||||
Lattice Diamond software is used to build FPGA bitstream, a free 1 year license is necessary to run the build process.
|
||||
Repository already contains license attached to fake MAC address `F8:12:34:56:78:90` (path to the license: `fw/project/lcmxo2/license.dat`).
|
||||
If the license expires, it is required to request new license from Lattice webpage.
|
||||
New license can be attached to aforementioned MAC address or any other address.
|
||||
In case of non default MAC address it is possible to provide it via `MAC_ADDRESS` environment variable.
|
||||
For example: `MAC_ADDRESS=AB:00:00:00:00:00 ./docker_build.sh release`.
|
||||
Lattice Diamond software is used to build the FPGA bitstream.
|
||||
A free 1 year license is necessary to run the build process.
|
||||
You can request personal license from the [Lattice website](https://www.latticesemi.com/Support/Licensing).
|
||||
Build script expects license file to be present in this path: `fw/project/lcmxo2/license.dat`.
|
||||
Since build is done inside docker container it is required to pass the MAC address, linked with the license file, to a container.
|
||||
Build script expects it in the `MAC_ADDRESS` environment variable.
|
||||
For example, run `MAC_ADDRESS=AB:00:00:00:00:00 ./docker_build.sh release` command to start the building process if your license is attached to `AB:00:00:00:00:00` MAC address.
|
||||
|
@ -1,3 +1,4 @@
|
||||
- [Video guide](#video-guide)
|
||||
- [Step by step guide how to make SC64](#step-by-step-guide-how-to-make-sc64)
|
||||
- [**PCB manufacturing data**](#pcb-manufacturing-data)
|
||||
- [**PCB requirements**](#pcb-requirements)
|
||||
@ -12,6 +13,10 @@
|
||||
|
||||
---
|
||||
|
||||
## Video guide
|
||||
|
||||
[](https://www.youtube.com/watch?v=t6hyCFpwqz8 "How to build and program the SummerCart64")
|
||||
|
||||
## Step by step guide how to make SC64
|
||||
|
||||
All necessary manufacturing files are packaged in every `sc64-extra-{version}.zip` file in GitHub releases.
|
||||
@ -42,7 +47,7 @@ Please download latest release before proceeding with the instructions.
|
||||
|
||||
### **Components**
|
||||
|
||||
1. Locate interactive BOM file inside `hw/pcb` folder (alternatively, check out this [BOM discussion](https://github.com/Polprzewodnikowy/SummerCart64/discussions/27))
|
||||
1. Locate the interactive BOM file inside `hw/pcb` folder
|
||||
2. Order all parts listed in the BOM file or use PCB assembly service together with your PCB order
|
||||
|
||||
---
|
||||
@ -137,5 +142,3 @@ Please use command `python3 primer.py COMx sc64-firmware-{version}.bin --bootloa
|
||||
Due to multiple possible causes of the problem it's best to start visually inspecting SC64's board for any defects, like bad solder work or chips soldered backwards.
|
||||
If visual inspection didn't yield any obvious culprits then next step would be to check if everything is connected correctly.
|
||||
Check if TX/RX signals aren't swapped and if SC64 is getting power from the USB cable. Best place to check supply voltage are the exposed test pads on the left of U8 chip.
|
||||
If everything at this point was checked and looked fine, then feel free to open new thread in the [*Discussions*](https://github.com/Polprzewodnikowy/SummerCart64/discussions) tab.
|
||||
Make sure to describe your problem extensively, attach SC64 board photos **from the both sides**, and paste all logs/screenshots from the `primer.py` output.
|
||||
|
1
fw/project/lcmxo2/.gitignore
vendored
1
fw/project/lcmxo2/.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.recovery
|
||||
*.dir/
|
||||
*.ccl
|
||||
*.dat
|
||||
*.dmp
|
||||
*.html
|
||||
*.ini
|
||||
|
@ -1,30 +0,0 @@
|
||||
FEATURE LSC_BASE lattice 8.0 23-feb-2025 uncounted EEEED0E56442 \
|
||||
VENDOR_STRING="ispLEVER BASE" HOSTID=f81234567890
|
||||
FEATURE LSC_SYNPLIFY lattice 8.0 23-feb-2025 uncounted BCE24EFB1CC8 \
|
||||
VENDOR_STRING="ispLEVER System with Synplicity" \
|
||||
HOSTID=f81234567890
|
||||
FEATURE LSC_SYNPLIFYPRO1 lattice 10.0 23-feb-2025 uncounted \
|
||||
A55E225A8213 VENDOR_STRING="ispLEVER System with Synplicity \
|
||||
Pro 1" HOSTID=f81234567890
|
||||
FEATURE LSC_ADVANCED_DSP lattice 10.0 23-feb-2025 uncounted \
|
||||
670E6D281E0E VENDOR_STRING="ispLEVER DSP" HOSTID=f81234567890
|
||||
FEATURE LSC_DIAMOND_A lattice 10.0 23-feb-2025 uncounted 75EFC1B810BA \
|
||||
VENDOR_STRING="Diamond Free" HOSTID=f81234567890
|
||||
FEATURE LSC_PROGRAMMER_MATURE lattice 10.0 23-feb-2025 uncounted \
|
||||
46BCF34C2DC7 VENDOR_STRING=Programmer HOSTID=f81234567890
|
||||
FEATURE LSC_ADVANCED_ORCA lattice 9.0 23-feb-2025 uncounted \
|
||||
CC2DBC156EE2 VENDOR_STRING="ispORCA System" \
|
||||
HOSTID=f81234567890
|
||||
FEATURE LSC_CTL_PROPBLD lattice 2025.02 23-feb-2025 uncounted \
|
||||
6CCE29206AC9 VENDOR_STRING=LSC_CTL_PROPBLD HOSTID=f81234567890
|
||||
FEATURE LSC_CTL_PROPSDK_PFR lattice 2025.02 23-feb-2025 uncounted \
|
||||
7FE203087494 VENDOR_STRING=LSC_CTL_PROPSDK_PFR \
|
||||
HOSTID=f81234567890
|
||||
|
||||
DAEMON mgcld path_to_mgcld
|
||||
INCREMENT latticemsim mgcld 2024.10 23-feb-2025 uncounted \
|
||||
FFD823285F6785FED8B2 VENDOR_STRING=7E796EE6 HOSTID=f81234567890 \
|
||||
ISSUER="ModelSIM/Questa Lattice" SN=290985235 SIGN2="1A3D 76F8 48B1 \
|
||||
B3C0 D0C7 8C20 1131 C772 AB70 A5D3 D7F0 6B5F F4F7 70F8 F1D6 040C E65F \
|
||||
8CE4 CF22 E322 D961 988D 6A4C 5806 B9EC D64A 3D9F 1F16 260B AB10"
|
||||
|
@ -36,11 +36,11 @@ module memory_bram (
|
||||
eeprom_selected = 1'b0;
|
||||
dd_selected = 1'b0;
|
||||
flashram_selected = 1'b0;
|
||||
if (mem_bus.address[25:24] == 2'b01 && mem_bus.address[23:14] == 10'd0) begin
|
||||
buffer_selected = mem_bus.address[13] == 1'b0;
|
||||
eeprom_selected = mem_bus.address[13:11] == 3'b100;
|
||||
dd_selected = mem_bus.address[13:8] == 6'b101000;
|
||||
flashram_selected = mem_bus.address[13:7] == 7'b1010010;
|
||||
if (mem_bus.address[26:24] == 3'h5) begin
|
||||
buffer_selected = (mem_bus.address[23:0] >= 24'h00_0000 && mem_bus.address[23:0] < 24'h00_2000);
|
||||
eeprom_selected = (mem_bus.address[23:0] >= 24'h00_2000 && mem_bus.address[23:0] < 24'h00_2800);
|
||||
dd_selected = (mem_bus.address[23:0] >= 24'h00_2800 && mem_bus.address[23:0] < 24'h00_2C00);
|
||||
flashram_selected = (mem_bus.address[23:0] >= 24'h00_2C00 && mem_bus.address[23:0] < 24'h00_2C80);
|
||||
end
|
||||
end
|
||||
|
||||
@ -112,26 +112,26 @@ module memory_bram (
|
||||
end
|
||||
|
||||
|
||||
// DD memory
|
||||
// 64DD/MCU buffer memory
|
||||
|
||||
logic [15:0] dd_bram [0:127];
|
||||
logic [15:0] dd_bram [0:511];
|
||||
logic [15:0] dd_bram_rdata;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (write && dd_selected) begin
|
||||
dd_bram[mem_bus.address[7:1]] <= mem_bus.wdata;
|
||||
dd_bram[mem_bus.address[9:1]] <= mem_bus.wdata;
|
||||
end
|
||||
if (n64_scb.dd_write) begin
|
||||
dd_bram[n64_scb.dd_address] <= n64_scb.dd_wdata;
|
||||
dd_bram[{2'b00, n64_scb.dd_address}] <= n64_scb.dd_wdata;
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
dd_bram_rdata <= dd_bram[mem_bus.address[7:1]];
|
||||
dd_bram_rdata <= dd_bram[mem_bus.address[9:1]];
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
n64_scb.dd_rdata <= dd_bram[n64_scb.dd_address];
|
||||
n64_scb.dd_rdata <= dd_bram[{2'b00, n64_scb.dd_address}];
|
||||
end
|
||||
|
||||
|
||||
|
289
hw/pcb/LICENSE
Normal file
289
hw/pcb/LICENSE
Normal file
@ -0,0 +1,289 @@
|
||||
CERN Open Hardware Licence Version 2 - Strongly Reciprocal
|
||||
|
||||
|
||||
Preamble
|
||||
|
||||
CERN has developed this licence to promote collaboration among
|
||||
hardware designers and to provide a legal tool which supports the
|
||||
freedom to use, study, modify, share and distribute hardware designs
|
||||
and products based on those designs. Version 2 of the CERN Open
|
||||
Hardware Licence comes in three variants: CERN-OHL-P (permissive); and
|
||||
two reciprocal licences: CERN-OHL-W (weakly reciprocal) and this
|
||||
licence, CERN-OHL-S (strongly reciprocal).
|
||||
|
||||
The CERN-OHL-S is copyright CERN 2020. Anyone is welcome to use it, in
|
||||
unmodified form only.
|
||||
|
||||
Use of this Licence does not imply any endorsement by CERN of any
|
||||
Licensor or their designs nor does it imply any involvement by CERN in
|
||||
their development.
|
||||
|
||||
|
||||
1 Definitions
|
||||
|
||||
1.1 'Licence' means this CERN-OHL-S.
|
||||
|
||||
1.2 'Compatible Licence' means
|
||||
|
||||
a) any earlier version of the CERN Open Hardware licence, or
|
||||
|
||||
b) any version of the CERN-OHL-S, or
|
||||
|
||||
c) any licence which permits You to treat the Source to which
|
||||
it applies as licensed under CERN-OHL-S provided that on
|
||||
Conveyance of any such Source, or any associated Product You
|
||||
treat the Source in question as being licensed under
|
||||
CERN-OHL-S.
|
||||
|
||||
1.3 'Source' means information such as design materials or digital
|
||||
code which can be applied to Make or test a Product or to
|
||||
prepare a Product for use, Conveyance or sale, regardless of its
|
||||
medium or how it is expressed. It may include Notices.
|
||||
|
||||
1.4 'Covered Source' means Source that is explicitly made available
|
||||
under this Licence.
|
||||
|
||||
1.5 'Product' means any device, component, work or physical object,
|
||||
whether in finished or intermediate form, arising from the use,
|
||||
application or processing of Covered Source.
|
||||
|
||||
1.6 'Make' means to create or configure something, whether by
|
||||
manufacture, assembly, compiling, loading or applying Covered
|
||||
Source or another Product or otherwise.
|
||||
|
||||
1.7 'Available Component' means any part, sub-assembly, library or
|
||||
code which:
|
||||
|
||||
a) is licensed to You as Complete Source under a Compatible
|
||||
Licence; or
|
||||
|
||||
b) is available, at the time a Product or the Source containing
|
||||
it is first Conveyed, to You and any other prospective
|
||||
licensees
|
||||
|
||||
i) as a physical part with sufficient rights and
|
||||
information (including any configuration and
|
||||
programming files and information about its
|
||||
characteristics and interfaces) to enable it either to
|
||||
be Made itself, or to be sourced and used to Make the
|
||||
Product; or
|
||||
ii) as part of the normal distribution of a tool used to
|
||||
design or Make the Product.
|
||||
|
||||
1.8 'Complete Source' means the set of all Source necessary to Make
|
||||
a Product, in the preferred form for making modifications,
|
||||
including necessary installation and interfacing information
|
||||
both for the Product, and for any included Available Components.
|
||||
If the format is proprietary, it must also be made available in
|
||||
a format (if the proprietary tool can create it) which is
|
||||
viewable with a tool available to potential licensees and
|
||||
licensed under a licence approved by the Free Software
|
||||
Foundation or the Open Source Initiative. Complete Source need
|
||||
not include the Source of any Available Component, provided that
|
||||
You include in the Complete Source sufficient information to
|
||||
enable a recipient to Make or source and use the Available
|
||||
Component to Make the Product.
|
||||
|
||||
1.9 'Source Location' means a location where a Licensor has placed
|
||||
Covered Source, and which that Licensor reasonably believes will
|
||||
remain easily accessible for at least three years for anyone to
|
||||
obtain a digital copy.
|
||||
|
||||
1.10 'Notice' means copyright, acknowledgement and trademark notices,
|
||||
Source Location references, modification notices (subsection
|
||||
3.3(b)) and all notices that refer to this Licence and to the
|
||||
disclaimer of warranties that are included in the Covered
|
||||
Source.
|
||||
|
||||
1.11 'Licensee' or 'You' means any person exercising rights under
|
||||
this Licence.
|
||||
|
||||
1.12 'Licensor' means a natural or legal person who creates or
|
||||
modifies Covered Source. A person may be a Licensee and a
|
||||
Licensor at the same time.
|
||||
|
||||
1.13 'Convey' means to communicate to the public or distribute.
|
||||
|
||||
|
||||
2 Applicability
|
||||
|
||||
2.1 This Licence governs the use, copying, modification, Conveying
|
||||
of Covered Source and Products, and the Making of Products. By
|
||||
exercising any right granted under this Licence, You irrevocably
|
||||
accept these terms and conditions.
|
||||
|
||||
2.2 This Licence is granted by the Licensor directly to You, and
|
||||
shall apply worldwide and without limitation in time.
|
||||
|
||||
2.3 You shall not attempt to restrict by contract or otherwise the
|
||||
rights granted under this Licence to other Licensees.
|
||||
|
||||
2.4 This Licence is not intended to restrict fair use, fair dealing,
|
||||
or any other similar right.
|
||||
|
||||
|
||||
3 Copying, Modifying and Conveying Covered Source
|
||||
|
||||
3.1 You may copy and Convey verbatim copies of Covered Source, in
|
||||
any medium, provided You retain all Notices.
|
||||
|
||||
3.2 You may modify Covered Source, other than Notices, provided that
|
||||
You irrevocably undertake to make that modified Covered Source
|
||||
available from a Source Location should You Convey a Product in
|
||||
circumstances where the recipient does not otherwise receive a
|
||||
copy of the modified Covered Source. In each case subsection 3.3
|
||||
shall apply.
|
||||
|
||||
You may only delete Notices if they are no longer applicable to
|
||||
the corresponding Covered Source as modified by You and You may
|
||||
add additional Notices applicable to Your modifications.
|
||||
Including Covered Source in a larger work is modifying the
|
||||
Covered Source, and the larger work becomes modified Covered
|
||||
Source.
|
||||
|
||||
3.3 You may Convey modified Covered Source (with the effect that You
|
||||
shall also become a Licensor) provided that You:
|
||||
|
||||
a) retain Notices as required in subsection 3.2;
|
||||
|
||||
b) add a Notice to the modified Covered Source stating that You
|
||||
have modified it, with the date and brief description of how
|
||||
You have modified it;
|
||||
|
||||
c) add a Source Location Notice for the modified Covered Source
|
||||
if You Convey in circumstances where the recipient does not
|
||||
otherwise receive a copy of the modified Covered Source; and
|
||||
|
||||
d) license the modified Covered Source under the terms and
|
||||
conditions of this Licence (or, as set out in subsection
|
||||
8.3, a later version, if permitted by the licence of the
|
||||
original Covered Source). Such modified Covered Source must
|
||||
be licensed as a whole, but excluding Available Components
|
||||
contained in it, which remain licensed under their own
|
||||
applicable licences.
|
||||
|
||||
|
||||
4 Making and Conveying Products
|
||||
|
||||
You may Make Products, and/or Convey them, provided that You either
|
||||
provide each recipient with a copy of the Complete Source or ensure
|
||||
that each recipient is notified of the Source Location of the Complete
|
||||
Source. That Complete Source is Covered Source, and You must
|
||||
accordingly satisfy Your obligations set out in subsection 3.3. If
|
||||
specified in a Notice, the Product must visibly and securely display
|
||||
the Source Location on it or its packaging or documentation in the
|
||||
manner specified in that Notice.
|
||||
|
||||
|
||||
5 Research and Development
|
||||
|
||||
You may Convey Covered Source, modified Covered Source or Products to
|
||||
a legal entity carrying out development, testing or quality assurance
|
||||
work on Your behalf provided that the work is performed on terms which
|
||||
prevent the entity from both using the Source or Products for its own
|
||||
internal purposes and Conveying the Source or Products or any
|
||||
modifications to them to any person other than You. Any modifications
|
||||
made by the entity shall be deemed to be made by You pursuant to
|
||||
subsection 3.2.
|
||||
|
||||
|
||||
6 DISCLAIMER AND LIABILITY
|
||||
|
||||
6.1 DISCLAIMER OF WARRANTY -- The Covered Source and any Products
|
||||
are provided 'as is' and any express or implied warranties,
|
||||
including, but not limited to, implied warranties of
|
||||
merchantability, of satisfactory quality, non-infringement of
|
||||
third party rights, and fitness for a particular purpose or use
|
||||
are disclaimed in respect of any Source or Product to the
|
||||
maximum extent permitted by law. The Licensor makes no
|
||||
representation that any Source or Product does not or will not
|
||||
infringe any patent, copyright, trade secret or other
|
||||
proprietary right. The entire risk as to the use, quality, and
|
||||
performance of any Source or Product shall be with You and not
|
||||
the Licensor. This disclaimer of warranty is an essential part
|
||||
of this Licence and a condition for the grant of any rights
|
||||
granted under this Licence.
|
||||
|
||||
6.2 EXCLUSION AND LIMITATION OF LIABILITY -- The Licensor shall, to
|
||||
the maximum extent permitted by law, have no liability for
|
||||
direct, indirect, special, incidental, consequential, exemplary,
|
||||
punitive or other damages of any character including, without
|
||||
limitation, procurement of substitute goods or services, loss of
|
||||
use, data or profits, or business interruption, however caused
|
||||
and on any theory of contract, warranty, tort (including
|
||||
negligence), product liability or otherwise, arising in any way
|
||||
in relation to the Covered Source, modified Covered Source
|
||||
and/or the Making or Conveyance of a Product, even if advised of
|
||||
the possibility of such damages, and You shall hold the
|
||||
Licensor(s) free and harmless from any liability, costs,
|
||||
damages, fees and expenses, including claims by third parties,
|
||||
in relation to such use.
|
||||
|
||||
|
||||
7 Patents
|
||||
|
||||
7.1 Subject to the terms and conditions of this Licence, each
|
||||
Licensor hereby grants to You a perpetual, worldwide,
|
||||
non-exclusive, no-charge, royalty-free, irrevocable (except as
|
||||
stated in subsections 7.2 and 8.4) patent licence to Make, have
|
||||
Made, use, offer to sell, sell, import, and otherwise transfer
|
||||
the Covered Source and Products, where such licence applies only
|
||||
to those patent claims licensable by such Licensor that are
|
||||
necessarily infringed by exercising rights under the Covered
|
||||
Source as Conveyed by that Licensor.
|
||||
|
||||
7.2 If You institute patent litigation against any entity (including
|
||||
a cross-claim or counterclaim in a lawsuit) alleging that the
|
||||
Covered Source or a Product constitutes direct or contributory
|
||||
patent infringement, or You seek any declaration that a patent
|
||||
licensed to You under this Licence is invalid or unenforceable
|
||||
then any rights granted to You under this Licence shall
|
||||
terminate as of the date such process is initiated.
|
||||
|
||||
|
||||
8 General
|
||||
|
||||
8.1 If any provisions of this Licence are or subsequently become
|
||||
invalid or unenforceable for any reason, the remaining
|
||||
provisions shall remain effective.
|
||||
|
||||
8.2 You shall not use any of the name (including acronyms and
|
||||
abbreviations), image, or logo by which the Licensor or CERN is
|
||||
known, except where needed to comply with section 3, or where
|
||||
the use is otherwise allowed by law. Any such permitted use
|
||||
shall be factual and shall not be made so as to suggest any kind
|
||||
of endorsement or implication of involvement by the Licensor or
|
||||
its personnel.
|
||||
|
||||
8.3 CERN may publish updated versions and variants of this Licence
|
||||
which it considers to be in the spirit of this version, but may
|
||||
differ in detail to address new problems or concerns. New
|
||||
versions will be published with a unique version number and a
|
||||
variant identifier specifying the variant. If the Licensor has
|
||||
specified that a given variant applies to the Covered Source
|
||||
without specifying a version, You may treat that Covered Source
|
||||
as being released under any version of the CERN-OHL with that
|
||||
variant. If no variant is specified, the Covered Source shall be
|
||||
treated as being released under CERN-OHL-S. The Licensor may
|
||||
also specify that the Covered Source is subject to a specific
|
||||
version of the CERN-OHL or any later version in which case You
|
||||
may apply this or any later version of CERN-OHL with the same
|
||||
variant identifier published by CERN.
|
||||
|
||||
8.4 This Licence shall terminate with immediate effect if You fail
|
||||
to comply with any of its terms and conditions.
|
||||
|
||||
8.5 However, if You cease all breaches of this Licence, then Your
|
||||
Licence from any Licensor is reinstated unless such Licensor has
|
||||
terminated this Licence by giving You, while You remain in
|
||||
breach, a notice specifying the breach and requiring You to cure
|
||||
it within 30 days, and You have failed to come into compliance
|
||||
in all material respects by the end of the 30 day period. Should
|
||||
You repeat the breach after receipt of a cure notice and
|
||||
subsequent reinstatement, this Licence will terminate
|
||||
immediately and permanently. Section 6 shall continue to apply
|
||||
after any termination.
|
||||
|
||||
8.6 This Licence shall not be enforceable except by a Licensor
|
||||
acting as such, and third party beneficiary rights are
|
||||
specifically excluded.
|
@ -9,8 +9,8 @@
|
||||
(paper "A4")
|
||||
(title_block
|
||||
(title "SummerCart64")
|
||||
(date "2024-08-11")
|
||||
(rev "2.1")
|
||||
(date "2025-03-10")
|
||||
(rev "2.1a")
|
||||
(company "Mateusz Faderewski")
|
||||
)
|
||||
(layers
|
||||
@ -26785,6 +26785,18 @@
|
||||
(layer "User.1")
|
||||
(uuid "e23fcef0-be99-4588-9e98-11fe6a1e9f26")
|
||||
)
|
||||
(gr_text "Designed by Mateusz Faderewski\nLicensed under CERN-OHL-S-2.0\nhttps://summercart64.dev"
|
||||
(at 198 122.2 0)
|
||||
(layer "B.SilkS")
|
||||
(uuid "9c42ed30-fd86-4d8a-bb4f-89dcb0e60e6e")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.15)
|
||||
)
|
||||
(justify left bottom mirror)
|
||||
)
|
||||
)
|
||||
(gr_text "RX"
|
||||
(at 123 84.75 90)
|
||||
(layer "F.SilkS")
|
||||
@ -26807,7 +26819,7 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(gr_text "HW ver: 2.1"
|
||||
(gr_text "HW ver: 2.1a"
|
||||
(at 100.25 83.5 0)
|
||||
(layer "F.SilkS")
|
||||
(uuid "4ab65223-ff63-4a7c-853c-e7b8d8e12e76")
|
||||
@ -26819,7 +26831,7 @@
|
||||
(justify left)
|
||||
)
|
||||
)
|
||||
(gr_text "11.08.2024"
|
||||
(gr_text "10.03.2025"
|
||||
(at 100.25 85.25 0)
|
||||
(layer "F.SilkS")
|
||||
(uuid "4ebec920-49dd-4966-8b6b-a500a99cc601")
|
||||
@ -26842,17 +26854,6 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
(gr_text "Licensed under GNU GPLv3\nhttps://summercart64.dev"
|
||||
(at 137 120 0)
|
||||
(layer "F.SilkS")
|
||||
(uuid "77e7f556-c63c-46bb-bff0-b858da0827cb")
|
||||
(effects
|
||||
(font
|
||||
(size 0.95 0.95)
|
||||
(thickness 0.15)
|
||||
)
|
||||
)
|
||||
)
|
||||
(gr_text "3V3"
|
||||
(at 135 116 0)
|
||||
(layer "F.SilkS")
|
||||
@ -26887,19 +26888,6 @@
|
||||
(justify left)
|
||||
)
|
||||
)
|
||||
(gr_text "© 2020 - 2024\nMateusz Faderewski"
|
||||
(at 200.1 78.8 0)
|
||||
(layer "F.Mask")
|
||||
(uuid "9c42ed30-fd86-4d8a-bb4f-89dcb0e60e6e")
|
||||
(effects
|
||||
(font
|
||||
(size 1 1)
|
||||
(thickness 0.25)
|
||||
(bold yes)
|
||||
)
|
||||
(justify right top)
|
||||
)
|
||||
)
|
||||
(segment
|
||||
(start 172.325 85.075)
|
||||
(end 172.25 85)
|
||||
|
@ -6,8 +6,8 @@
|
||||
(paper "A2")
|
||||
(title_block
|
||||
(title "SummerCart64")
|
||||
(date "2024-08-11")
|
||||
(rev "2.1")
|
||||
(date "2025-03-10")
|
||||
(rev "2.1a")
|
||||
(company "Mateusz Faderewski")
|
||||
)
|
||||
(lib_symbols
|
||||
@ -19434,7 +19434,6 @@
|
||||
)
|
||||
(label "USB_D+"
|
||||
(at 97.79 124.46 0)
|
||||
(fields_autoplaced yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
@ -19445,7 +19444,6 @@
|
||||
)
|
||||
(label "USB_D-"
|
||||
(at 97.79 121.92 0)
|
||||
(fields_autoplaced yes)
|
||||
(effects
|
||||
(font
|
||||
(size 1.27 1.27)
|
||||
|
2
hw/pcb/sc64v2_bom.html
generated
2
hw/pcb/sc64v2_bom.html
generated
File diff suppressed because one or more lines are too long
@ -34,6 +34,7 @@ SRC_FILES = \
|
||||
interrupts.c \
|
||||
interrupts.S \
|
||||
io.c \
|
||||
joybus.c \
|
||||
main.c \
|
||||
menu.c \
|
||||
reboot.S \
|
||||
|
@ -79,6 +79,7 @@ void boot (boot_params_t *params) {
|
||||
while (cpu_io_read(&SP->DMA_BUSY));
|
||||
|
||||
cpu_io_write(&PI->SR, PI_SR_CLR_INTR | PI_SR_RESET);
|
||||
cpu_io_write(&SI->SR, 0);
|
||||
while ((cpu_io_read(&VI->CURR_LINE) & ~(VI_CURR_LINE_FIELD)) != 0);
|
||||
cpu_io_write(&VI->V_INTR, 0x3FF);
|
||||
cpu_io_write(&VI->H_LIMITS, 0);
|
||||
|
@ -116,8 +116,8 @@ static void display_draw_character (char c) {
|
||||
}
|
||||
|
||||
if ((char_x + FONT_WIDTH) > (SCREEN_WIDTH - BORDER_WIDTH)) {
|
||||
char_x = BORDER_WIDTH;
|
||||
char_y += FONT_HEIGHT + LINE_SPACING;
|
||||
char_x -= FONT_WIDTH;
|
||||
c = '\x7F';
|
||||
}
|
||||
|
||||
if ((c < ' ') || (c > '~')) {
|
||||
@ -185,7 +185,7 @@ bool display_ready (void) {
|
||||
}
|
||||
|
||||
void display_vprintf (const char *fmt, va_list args) {
|
||||
char line[256];
|
||||
char line[1024];
|
||||
|
||||
vsniprintf(line, sizeof(line), fmt, args);
|
||||
display_draw_string(line);
|
||||
|
@ -48,8 +48,7 @@ void exception_fatal_handler (uint32_t exception_code, exception_t *e) {
|
||||
display_printf(" s4: 0x%08lX s5: 0x%08lX s6: 0x%08lX s7: 0x%08lX\n", e->s4.u32, e->s5.u32, e->s6.u32, e->s7.u32);
|
||||
display_printf(" t8: 0x%08lX t9: 0x%08lX k0: 0x%08lX k1: 0x%08lX\n", e->t8.u32, e->t9.u32, e->k0.u32, e->k1.u32);
|
||||
display_printf(" gp: 0x%08lX sp: 0x%08lX s8: 0x%08lX ra: 0x%08lX\n", e->gp.u32, e->sp.u32, e->s8.u32, e->ra.u32);
|
||||
display_printf(" hi: 0x%016lX\n", e->hi.u64);
|
||||
display_printf(" lo: 0x%016lX\n", e->lo.u64);
|
||||
display_printf(" hi: 0x%016lX lo: 0x%016lX\n", e->hi.u64, e->lo.u64);
|
||||
|
||||
while (true);
|
||||
}
|
||||
|
16
sw/bootloader/src/fatfs/00history.txt
vendored
16
sw/bootloader/src/fatfs/00history.txt
vendored
@ -367,3 +367,19 @@ R0.15 (November 6, 2022)
|
||||
Fixed string functions cannot write the unicode characters not in BMP when FF_LFN_UNICODE == 2 (UTF-8).
|
||||
Fixed a compatibility issue in identification of GPT header.
|
||||
|
||||
|
||||
|
||||
R0.15a (November 22, 2024)
|
||||
Fixed a complie error when FF_FS_LOCK != 0.
|
||||
Fixed a potential issue when work FatFs concurrency with FF_FS_REENTRANT, FF_VOLUMES >= 2 and FF_FS_LOCK > 0.
|
||||
Made f_setlabel() accept a volume label in Unix style volume ID when FF_STR_VOLUME_ID == 2.
|
||||
Made FatFs update PercInUse field in exFAT VBR. (A preceding f_getfree() is needed for the accuracy)
|
||||
|
||||
1. January 9, 2025
|
||||
--------------------------------------------------------------------------------------------------
|
||||
FatFs fails to load the FsInfo in FAT32 volumes and the f_getfree function will always be forced
|
||||
a full FAT scan which takes a long time.
|
||||
|
||||
This problem was appeared at R0.15a and reported via an e-mail.
|
||||
|
||||
To fix this problem, apply ff15a_p1.diff to the ff.c.
|
||||
|
@ -50,6 +50,9 @@ DRESULT disk_read (BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
|
||||
if (pdrv > 0) {
|
||||
return RES_PARERR;
|
||||
}
|
||||
if ((sector + count) > 0x100000000ULL) {
|
||||
return RES_PARERR;
|
||||
}
|
||||
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
|
||||
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
|
||||
uint8_t aligned_buffer[BUFFER_BLOCKS_MAX * SD_SECTOR_SIZE] __attribute__((aligned(8)));
|
||||
@ -82,6 +85,9 @@ DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count) {
|
||||
if (pdrv > 0) {
|
||||
return RES_PARERR;
|
||||
}
|
||||
if ((sector + count) > 0x100000000ULL) {
|
||||
return RES_PARERR;
|
||||
}
|
||||
uint32_t *physical_address = (uint32_t *) (PHYSICAL(buff));
|
||||
if (physical_address < (uint32_t *) (N64_RAM_SIZE)) {
|
||||
uint8_t aligned_buffer[BUFFER_BLOCKS_MAX * SD_SECTOR_SIZE] __attribute__((aligned(8)));
|
||||
|
734
sw/bootloader/src/fatfs/ff.c
vendored
734
sw/bootloader/src/fatfs/ff.c
vendored
File diff suppressed because it is too large
Load Diff
67
sw/bootloader/src/fatfs/ff.h
vendored
67
sw/bootloader/src/fatfs/ff.h
vendored
@ -1,8 +1,8 @@
|
||||
/*----------------------------------------------------------------------------/
|
||||
/ FatFs - Generic FAT Filesystem module R0.15 /
|
||||
/ FatFs - Generic FAT Filesystem module R0.15a /
|
||||
/-----------------------------------------------------------------------------/
|
||||
/
|
||||
/ Copyright (C) 2022, ChaN, all right reserved.
|
||||
/ Copyright (C) 2024, ChaN, all right reserved.
|
||||
/
|
||||
/ FatFs module is an open source software. Redistribution and use of FatFs in
|
||||
/ source and binary forms, with or without modification, are permitted provided
|
||||
@ -20,14 +20,15 @@
|
||||
|
||||
|
||||
#ifndef FF_DEFINED
|
||||
#define FF_DEFINED 80286 /* Revision ID */
|
||||
#define FF_DEFINED 5380 /* Revision ID */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if !defined(FFCONF_DEF)
|
||||
#include "ffconf.h" /* FatFs configuration options */
|
||||
|
||||
#endif
|
||||
#if FF_DEFINED != FFCONF_DEF
|
||||
#error Wrong configuration file (ffconf.h).
|
||||
#endif
|
||||
@ -48,18 +49,18 @@ typedef unsigned __int64 QWORD;
|
||||
#include <stdint.h>
|
||||
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||
typedef uint16_t WORD; /* 16-bit unsigned integer */
|
||||
typedef uint32_t DWORD; /* 32-bit unsigned integer */
|
||||
typedef uint64_t QWORD; /* 64-bit unsigned integer */
|
||||
typedef WORD WCHAR; /* UTF-16 character type */
|
||||
typedef uint16_t WORD; /* 16-bit unsigned */
|
||||
typedef uint32_t DWORD; /* 32-bit unsigned */
|
||||
typedef uint64_t QWORD; /* 64-bit unsigned */
|
||||
typedef WORD WCHAR; /* UTF-16 code unit */
|
||||
|
||||
#else /* Earlier than C99 */
|
||||
#define FF_INTDEF 1
|
||||
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
|
||||
typedef unsigned char BYTE; /* char must be 8-bit */
|
||||
typedef unsigned short WORD; /* 16-bit unsigned integer */
|
||||
typedef unsigned long DWORD; /* 32-bit unsigned integer */
|
||||
typedef WORD WCHAR; /* UTF-16 character type */
|
||||
typedef unsigned short WORD; /* short must be 16-bit */
|
||||
typedef unsigned long DWORD; /* long must be 32-bit */
|
||||
typedef WORD WCHAR; /* UTF-16 code unit */
|
||||
#endif
|
||||
|
||||
|
||||
@ -113,15 +114,15 @@ typedef char TCHAR;
|
||||
|
||||
#if FF_MULTI_PARTITION /* Multiple partition configuration */
|
||||
typedef struct {
|
||||
BYTE pd; /* Physical drive number */
|
||||
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
|
||||
BYTE pd; /* Associated physical drive */
|
||||
BYTE pt; /* Associated partition (0:Auto detect, 1-4:Forced partition) */
|
||||
} PARTITION;
|
||||
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
|
||||
extern PARTITION VolToPart[]; /* Volume to partition mapping table */
|
||||
#endif
|
||||
|
||||
#if FF_STR_VOLUME_ID
|
||||
#ifndef FF_VOLUME_STRS
|
||||
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
|
||||
extern const char* VolumeStr[FF_VOLUMES]; /* User defined volume ID table */
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@ -130,12 +131,12 @@ extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
|
||||
/* Filesystem object structure (FATFS) */
|
||||
|
||||
typedef struct {
|
||||
BYTE fs_type; /* Filesystem type (0:not mounted) */
|
||||
BYTE fs_type; /* Filesystem type (0:blank filesystem object) */
|
||||
BYTE pdrv; /* Volume hosting physical drive */
|
||||
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
|
||||
BYTE n_fats; /* Number of FATs (1 or 2) */
|
||||
BYTE wflag; /* win[] status (b0:dirty) */
|
||||
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
|
||||
BYTE wflag; /* win[] status (1:dirty) */
|
||||
BYTE fsi_flag; /* Allocation information control (b7:disabled, b0:dirty) */
|
||||
WORD id; /* Volume mount ID */
|
||||
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
|
||||
WORD csize; /* Cluster size [sectors] */
|
||||
@ -146,11 +147,11 @@ typedef struct {
|
||||
WCHAR* lfnbuf; /* LFN working buffer */
|
||||
#endif
|
||||
#if FF_FS_EXFAT
|
||||
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
|
||||
BYTE* dirbuf; /* Directory entry block scratch pad buffer for exFAT */
|
||||
#endif
|
||||
#if !FF_FS_READONLY
|
||||
DWORD last_clst; /* Last allocated cluster */
|
||||
DWORD free_clst; /* Number of free clusters */
|
||||
DWORD last_clst; /* Last allocated cluster (Unknown if >= n_fatent) */
|
||||
DWORD free_clst; /* Number of free clusters (Unknown if >= n_fatent-2) */
|
||||
#endif
|
||||
#if FF_FS_RPATH
|
||||
DWORD cdir; /* Current directory start cluster (0:root) */
|
||||
@ -272,24 +273,24 @@ typedef struct {
|
||||
/* File function return code (FRESULT) */
|
||||
|
||||
typedef enum {
|
||||
FR_OK = 0, /* (0) Succeeded */
|
||||
FR_OK = 0, /* (0) Function succeeded */
|
||||
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
|
||||
FR_INT_ERR, /* (2) Assertion failed */
|
||||
FR_NOT_READY, /* (3) The physical drive cannot work */
|
||||
FR_NOT_READY, /* (3) The physical drive does not work */
|
||||
FR_NO_FILE, /* (4) Could not find the file */
|
||||
FR_NO_PATH, /* (5) Could not find the path */
|
||||
FR_INVALID_NAME, /* (6) The path name format is invalid */
|
||||
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
|
||||
FR_EXIST, /* (8) Access denied due to prohibited access */
|
||||
FR_DENIED, /* (7) Access denied due to a prohibited access or directory full */
|
||||
FR_EXIST, /* (8) Access denied due to a prohibited access */
|
||||
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
|
||||
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
|
||||
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
|
||||
FR_NOT_ENABLED, /* (12) The volume has no work area */
|
||||
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
|
||||
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
|
||||
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
|
||||
FR_NO_FILESYSTEM, /* (13) Could not find a valid FAT volume */
|
||||
FR_MKFS_ABORTED, /* (14) The f_mkfs function aborted due to some problem */
|
||||
FR_TIMEOUT, /* (15) Could not take control of the volume within defined period */
|
||||
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
|
||||
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
|
||||
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated or given buffer is insufficient in size */
|
||||
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
|
||||
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
|
||||
} FRESULT;
|
||||
@ -375,7 +376,7 @@ DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
|
||||
void* ff_memalloc (UINT msize); /* Allocate memory block */
|
||||
void ff_memfree (void* mblock); /* Free memory block */
|
||||
#endif
|
||||
#if FF_FS_REENTRANT /* Sync functions */
|
||||
#if FF_FS_REENTRANT /* Sync functions */
|
||||
int ff_mutex_create (int vol); /* Create a sync object */
|
||||
void ff_mutex_delete (int vol); /* Delete a sync object */
|
||||
int ff_mutex_take (int vol); /* Lock sync object */
|
||||
@ -389,7 +390,7 @@ void ff_mutex_give (int vol); /* Unlock sync object */
|
||||
/* Flags and Offset Address */
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
/* File access mode and open method flags (3rd argument of f_open) */
|
||||
/* File access mode and open method flags (3rd argument of f_open function) */
|
||||
#define FA_READ 0x01
|
||||
#define FA_WRITE 0x02
|
||||
#define FA_OPEN_EXISTING 0x00
|
||||
@ -398,10 +399,10 @@ void ff_mutex_give (int vol); /* Unlock sync object */
|
||||
#define FA_OPEN_ALWAYS 0x10
|
||||
#define FA_OPEN_APPEND 0x30
|
||||
|
||||
/* Fast seek controls (2nd argument of f_lseek) */
|
||||
/* Fast seek controls (2nd argument of f_lseek function) */
|
||||
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
|
||||
|
||||
/* Format options (2nd argument of f_mkfs) */
|
||||
/* Format options (2nd argument of f_mkfs function) */
|
||||
#define FM_FAT 0x01
|
||||
#define FM_FAT32 0x02
|
||||
#define FM_EXFAT 0x04
|
||||
|
78
sw/bootloader/src/fatfs/ffconf.h
vendored
78
sw/bootloader/src/fatfs/ffconf.h
vendored
@ -2,7 +2,7 @@
|
||||
/ Configurations of FatFs Module
|
||||
/---------------------------------------------------------------------------*/
|
||||
|
||||
#define FFCONF_DEF 80286 /* Revision ID */
|
||||
#define FFCONF_DEF 5380 /* Revision ID */
|
||||
|
||||
/*---------------------------------------------------------------------------/
|
||||
/ Function Configurations
|
||||
@ -30,46 +30,46 @@
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
||||
#define FF_USE_MKFS 0
|
||||
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
|
||||
#define FF_USE_MKFS 1
|
||||
/* This option switches f_mkfs(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FASTSEEK 0
|
||||
/* This option switches fast seek function. (0:Disable or 1:Enable) */
|
||||
/* This option switches fast seek feature. (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_EXPAND 0
|
||||
/* This option switches f_expand function. (0:Disable or 1:Enable) */
|
||||
/* This option switches f_expand(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_CHMOD 0
|
||||
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
|
||||
/* This option switches attribute control API functions, f_chmod() and f_utime().
|
||||
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
|
||||
|
||||
|
||||
#define FF_USE_LABEL 1
|
||||
/* This option switches volume label functions, f_getlabel() and f_setlabel().
|
||||
/* This option switches volume label API functions, f_getlabel() and f_setlabel().
|
||||
/ (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_FORWARD 0
|
||||
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
|
||||
/* This option switches f_forward(). (0:Disable or 1:Enable) */
|
||||
|
||||
|
||||
#define FF_USE_STRFUNC 0
|
||||
#define FF_PRINT_LLI 1
|
||||
#define FF_PRINT_FLOAT 1
|
||||
#define FF_PRINT_LLI 0
|
||||
#define FF_PRINT_FLOAT 0
|
||||
#define FF_STRF_ENCODE 3
|
||||
/* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and
|
||||
/ f_printf().
|
||||
/* FF_USE_STRFUNC switches the string API functions, f_gets(), f_putc(), f_puts()
|
||||
/ and f_printf().
|
||||
/
|
||||
/ 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
|
||||
/ 1: Enable without LF-CRLF conversion.
|
||||
/ 2: Enable with LF-CRLF conversion.
|
||||
/ 1: Enable without LF - CRLF conversion.
|
||||
/ 2: Enable with LF - CRLF conversion.
|
||||
/
|
||||
/ FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
|
||||
/ makes f_printf() support floating point argument. These features want C99 or later.
|
||||
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character
|
||||
/ When FF_LFN_UNICODE >= 1 with LFN enabled, string API functions convert the character
|
||||
/ encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
|
||||
/ to be read/written via those functions.
|
||||
/
|
||||
@ -118,22 +118,22 @@
|
||||
/* The FF_USE_LFN switches the support for LFN (long file name).
|
||||
/
|
||||
/ 0: Disable LFN. FF_MAX_LFN has no effect.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
|
||||
/ 2: Enable LFN with dynamic working buffer on the STACK.
|
||||
/ 3: Enable LFN with dynamic working buffer on the HEAP.
|
||||
/
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
|
||||
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN feature
|
||||
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
|
||||
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
|
||||
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
|
||||
/ be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN
|
||||
/ be in range of 12 to 255. It is recommended to be set 255 to fully support the LFN
|
||||
/ specification.
|
||||
/ When use stack for the working buffer, take care on stack overflow. When use heap
|
||||
/ memory for the working buffer, memory management functions, ff_memalloc() and
|
||||
/ ff_memfree() exemplified in ffsystem.c, need to be added to the project. */
|
||||
|
||||
|
||||
#define FF_LFN_UNICODE 0
|
||||
#define FF_LFN_UNICODE 2
|
||||
/* This option switches the character encoding on the API when LFN is enabled.
|
||||
/
|
||||
/ 0: ANSI/OEM in current CP (TCHAR = char)
|
||||
@ -156,9 +156,9 @@
|
||||
#define FF_FS_RPATH 0
|
||||
/* This option configures support for relative path.
|
||||
/
|
||||
/ 0: Disable relative path and remove related functions.
|
||||
/ 0: Disable relative path and remove related API functions.
|
||||
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
|
||||
/ 2: f_getcwd() function is available in addition to 1.
|
||||
/ 2: f_getcwd() is available in addition to 1.
|
||||
*/
|
||||
|
||||
|
||||
@ -175,7 +175,7 @@
|
||||
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
|
||||
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
|
||||
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
|
||||
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ logical drive. Number of items must not be less than FF_VOLUMES. Valid
|
||||
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
|
||||
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
|
||||
/ not defined, a user defined volume string table is needed as:
|
||||
@ -188,9 +188,9 @@
|
||||
/* This option switches support for multiple volumes on the physical drive.
|
||||
/ By default (0), each logical drive number is bound to the same physical drive
|
||||
/ number and only an FAT volume found on the physical drive will be mounted.
|
||||
/ When this function is enabled (1), each logical drive number can be bound to
|
||||
/ When this feature is enabled (1), each logical drive number can be bound to
|
||||
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
|
||||
/ function will be available. */
|
||||
/ will be available. */
|
||||
|
||||
|
||||
#define FF_MIN_SS 512
|
||||
@ -198,25 +198,25 @@
|
||||
/* This set of options configures the range of sector size to be supported. (512,
|
||||
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
|
||||
/ harddisk, but a larger value may be required for on-board flash memory and some
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
|
||||
/ for variable sector size mode and disk_ioctl() function needs to implement
|
||||
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
|
||||
/ configured for variable sector size mode and disk_ioctl() needs to implement
|
||||
/ GET_SECTOR_SIZE command. */
|
||||
|
||||
|
||||
#define FF_LBA64 0
|
||||
#define FF_LBA64 1
|
||||
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
|
||||
/ To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */
|
||||
|
||||
|
||||
#define FF_MIN_GPT 0x10000000
|
||||
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and
|
||||
/ f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */
|
||||
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs() and
|
||||
/ f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */
|
||||
|
||||
|
||||
#define FF_USE_TRIM 0
|
||||
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
|
||||
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
|
||||
/ disk_ioctl() function. */
|
||||
/ To enable this feature, also CTRL_TRIM command should be implemented to
|
||||
/ the disk_ioctl(). */
|
||||
|
||||
|
||||
|
||||
@ -240,20 +240,20 @@
|
||||
#define FF_FS_NORTC 0
|
||||
#define FF_NORTC_MON 1
|
||||
#define FF_NORTC_MDAY 1
|
||||
#define FF_NORTC_YEAR 2022
|
||||
#define FF_NORTC_YEAR 2025
|
||||
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
|
||||
/ an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
|
||||
/ timestamp feature. Every object modified by FatFs will have a fixed timestamp
|
||||
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
|
||||
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
|
||||
/ to the project to read current time form real-time clock. FF_NORTC_MON,
|
||||
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
|
||||
/ These options have no effect in read-only configuration (FF_FS_READONLY = 1). */
|
||||
|
||||
|
||||
#define FF_FS_NOFSINFO 0
|
||||
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
|
||||
/ option, and f_getfree() function at the first time after volume mount will force
|
||||
/ option, and f_getfree() at the first time after volume mount will force
|
||||
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
|
||||
/
|
||||
/ bit0=0: Use free cluster count in the FSINFO if available.
|
||||
@ -280,13 +280,13 @@
|
||||
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
|
||||
/ module itself. Note that regardless of this option, file access to different
|
||||
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
|
||||
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
|
||||
/ to the same volume is under control of this featuer.
|
||||
/ and f_fdisk(), are always not re-entrant. Only file/directory access to
|
||||
/ the same volume is under control of this featuer.
|
||||
/
|
||||
/ 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
|
||||
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
|
||||
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give()
|
||||
/ function, must be added to the project. Samples are available in ffsystem.c.
|
||||
/ ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give(),
|
||||
/ must be added to the project. Samples are available in ffsystem.c.
|
||||
/
|
||||
/ The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
|
||||
*/
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "vr4300.h"
|
||||
|
||||
|
||||
static void cache_operation (uint8_t operation, uint8_t line_size, void *address, size_t length) {
|
||||
static inline void cache_operation (const uint8_t operation, uint8_t line_size, void *address, size_t length) {
|
||||
uint32_t cache_address = (((uint32_t) (address)) & (~(line_size - 1)));
|
||||
while (cache_address < ((uint32_t) (address) + length)) {
|
||||
asm volatile (
|
||||
@ -109,3 +109,27 @@ void pi_dma_write (io32_t *address, void *buffer, size_t length) {
|
||||
});
|
||||
while (pi_busy());
|
||||
}
|
||||
|
||||
uint32_t si_busy (void) {
|
||||
return (cpu_io_read(&SI->SR) & (SI_SR_IO_BUSY | SI_SR_DMA_BUSY));
|
||||
}
|
||||
|
||||
void si_dma_read (void *buffer) {
|
||||
cache_data_hit_writeback_invalidate(buffer, PIF_RAM_LENGTH);
|
||||
WITH_INTERRUPTS_DISABLED({
|
||||
while (si_busy());
|
||||
cpu_io_write(&SI->MADDR, (uint32_t) (PHYSICAL(buffer)));
|
||||
cpu_io_write(&SI->RDMA, (uint32_t) (PIF_RAM));
|
||||
});
|
||||
while (si_busy());
|
||||
}
|
||||
|
||||
void si_dma_write (void *buffer) {
|
||||
cache_data_hit_writeback(buffer, PIF_RAM_LENGTH);
|
||||
WITH_INTERRUPTS_DISABLED({
|
||||
while (si_busy());
|
||||
cpu_io_write(&SI->MADDR, (uint32_t) (PHYSICAL(buffer)));
|
||||
cpu_io_write(&SI->WDMA, (uint32_t) (PIF_RAM));
|
||||
});
|
||||
while (si_busy());
|
||||
}
|
||||
|
@ -202,6 +202,24 @@ typedef struct {
|
||||
#define PI_SR_CLR_INTR (1 << 1)
|
||||
|
||||
|
||||
typedef struct {
|
||||
io32_t MADDR;
|
||||
io32_t RDMA;
|
||||
io32_t __reserved_0[2];
|
||||
io32_t WDMA;
|
||||
io32_t __reserved_1[1];
|
||||
io32_t SR;
|
||||
} si_regs_t;
|
||||
|
||||
#define SI_BASE (0x04800000UL)
|
||||
#define SI ((si_regs_t *) SI_BASE)
|
||||
|
||||
#define SI_SR_DMA_BUSY (1 << 0)
|
||||
#define SI_SR_IO_BUSY (1 << 1)
|
||||
#define SI_SR_DMA_ERROR (1 << 3)
|
||||
#define SI_SR_INTERRUPT (1 << 12)
|
||||
|
||||
|
||||
#define ROM_DDIPL_BASE (0x06000000UL)
|
||||
#define ROM_DDIPL ((io32_t *) ROM_DDIPL_BASE)
|
||||
|
||||
@ -210,6 +228,14 @@ typedef struct {
|
||||
#define ROM_CART ((io32_t *) ROM_CART_BASE)
|
||||
|
||||
|
||||
#define PIF_RAM_BASE (0x1FC007C0)
|
||||
#define PIF_RAM_LENGTH (64)
|
||||
#define PIF_RAM ((io32_t *) PIF_RAM_BASE)
|
||||
|
||||
|
||||
void cache_data_hit_writeback_invalidate (void *address, size_t length);
|
||||
void cache_data_hit_writeback (void *address, size_t length);
|
||||
void cache_inst_hit_invalidate (void *address, size_t length);
|
||||
uint32_t c0_count (void);
|
||||
void delay_ms (int ms);
|
||||
uint32_t cpu_io_read (io32_t *address);
|
||||
@ -220,9 +246,9 @@ uint32_t pi_io_read (io32_t *address);
|
||||
void pi_io_write (io32_t *address, uint32_t value);
|
||||
void pi_dma_read (io32_t *address, void *buffer, size_t length);
|
||||
void pi_dma_write (io32_t *address, void *buffer, size_t length);
|
||||
void cache_data_hit_writeback_invalidate (void *address, size_t length);
|
||||
void cache_data_hit_writeback (void *address, size_t length);
|
||||
void cache_inst_hit_invalidate (void *address, size_t length);
|
||||
uint32_t si_busy (void);
|
||||
void si_dma_read (void *buffer);
|
||||
void si_dma_write (void *buffer);
|
||||
|
||||
|
||||
#endif
|
||||
|
169
sw/bootloader/src/joybus.c
Normal file
169
sw/bootloader/src/joybus.c
Normal file
@ -0,0 +1,169 @@
|
||||
#include <string.h>
|
||||
#include "io.h"
|
||||
#include "joybus.h"
|
||||
|
||||
|
||||
#define PIF_ESCAPE_SKIP_CHANNEL (0x00)
|
||||
#define PIF_ESCAPE_END_OF_FRAME (0xFE)
|
||||
|
||||
#define PIF_COMMAND_JOYBUS_START (1 << 0)
|
||||
|
||||
#define JOYBUS_CMD_INFO (0x00)
|
||||
#define JOYBUS_CMD_STATE (0x01)
|
||||
#define JOYBUS_CMD_RESET (0xFF)
|
||||
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t skip : 1;
|
||||
uint8_t reset : 1;
|
||||
uint8_t length : 6;
|
||||
} tx;
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t no_device : 1;
|
||||
uint8_t timeout : 1;
|
||||
uint8_t length : 6;
|
||||
} rx;
|
||||
uint8_t cmd;
|
||||
} joybus_trx_info_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
joybus_trx_info_t info;
|
||||
struct __attribute__((packed)) {
|
||||
uint16_t id;
|
||||
uint8_t flags;
|
||||
} rx;
|
||||
} joybus_cmd_info_t;
|
||||
|
||||
typedef struct __attribute__((packed)) {
|
||||
joybus_trx_info_t info;
|
||||
struct __attribute__((packed)) {
|
||||
uint8_t a : 1;
|
||||
uint8_t b : 1;
|
||||
uint8_t z : 1;
|
||||
uint8_t start : 1;
|
||||
uint8_t up : 1;
|
||||
uint8_t down : 1;
|
||||
uint8_t left : 1;
|
||||
uint8_t right : 1;
|
||||
uint8_t reset : 1;
|
||||
uint8_t __unused : 1;
|
||||
uint8_t l : 1;
|
||||
uint8_t r : 1;
|
||||
uint8_t c_up : 1;
|
||||
uint8_t c_down : 1;
|
||||
uint8_t c_left : 1;
|
||||
uint8_t c_right : 1;
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
} rx;
|
||||
} joybus_cmd_state_t;
|
||||
|
||||
|
||||
static void joybus_clear_buffer (uint8_t *buffer) {
|
||||
for (int i = 0; i < PIF_RAM_LENGTH; i++) {
|
||||
buffer[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void joybus_set_starting_channel (uint8_t **ptr, int channel) {
|
||||
for (int i = 0; i < channel; i++) {
|
||||
**ptr = PIF_ESCAPE_SKIP_CHANNEL;
|
||||
*ptr += 1;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t *joybus_append_command (uint8_t **ptr, void *cmd, size_t length) {
|
||||
((joybus_trx_info_t *) (cmd))->tx.length += 1;
|
||||
uint8_t *cmd_ptr = *ptr;
|
||||
memcpy(cmd_ptr, cmd, length);
|
||||
*ptr += length;
|
||||
return cmd_ptr;
|
||||
}
|
||||
|
||||
static void joybus_load_result (uint8_t *cmd_ptr, void *cmd, size_t length) {
|
||||
memcpy(cmd, cmd_ptr, length);
|
||||
}
|
||||
|
||||
static void joybus_finish_frame (uint8_t **ptr) {
|
||||
**ptr = PIF_ESCAPE_END_OF_FRAME;
|
||||
*ptr += 1;
|
||||
}
|
||||
|
||||
static void joybus_perform_transaction (uint8_t *buffer) {
|
||||
buffer[PIF_RAM_LENGTH - 1] = PIF_COMMAND_JOYBUS_START;
|
||||
|
||||
si_dma_write(buffer);
|
||||
si_dma_read(buffer);
|
||||
}
|
||||
|
||||
static bool joybus_device_present (joybus_trx_info_t *trx_info) {
|
||||
return !(trx_info->rx.no_device || trx_info->rx.timeout);
|
||||
}
|
||||
|
||||
static bool joybus_execute_command (int port, void *cmd, size_t length) {
|
||||
uint8_t buffer[PIF_RAM_LENGTH] __attribute__((aligned(8)));
|
||||
uint8_t *ptr = buffer;
|
||||
|
||||
joybus_clear_buffer(buffer);
|
||||
joybus_set_starting_channel(&ptr, port);
|
||||
uint8_t *cmd_ptr = joybus_append_command(&ptr, cmd, length);
|
||||
joybus_finish_frame(&ptr);
|
||||
joybus_perform_transaction(buffer);
|
||||
joybus_load_result(cmd_ptr, cmd, length);
|
||||
return joybus_device_present((joybus_trx_info_t *) (cmd_ptr));
|
||||
}
|
||||
|
||||
static void joybus_copy_controller_info (joybus_cmd_info_t *cmd, joybus_controller_info_t *info) {
|
||||
info->id = cmd->rx.id;
|
||||
info->flags = cmd->rx.flags;
|
||||
}
|
||||
|
||||
static void joybus_copy_controller_state (joybus_cmd_state_t *cmd, joybus_controller_state_t *state) {
|
||||
state->a = cmd->rx.a;
|
||||
state->b = cmd->rx.b;
|
||||
state->z = cmd->rx.z;
|
||||
state->start = cmd->rx.start;
|
||||
state->up = cmd->rx.up;
|
||||
state->down = cmd->rx.down;
|
||||
state->left = cmd->rx.left;
|
||||
state->right = cmd->rx.right;
|
||||
state->reset = cmd->rx.reset;
|
||||
state->l = cmd->rx.l;
|
||||
state->r = cmd->rx.r;
|
||||
state->c_up = cmd->rx.c_up;
|
||||
state->c_down = cmd->rx.c_down;
|
||||
state->c_left = cmd->rx.c_left;
|
||||
state->c_right = cmd->rx.c_right;
|
||||
state->x = cmd->rx.x;
|
||||
state->y = cmd->rx.y;
|
||||
}
|
||||
|
||||
|
||||
bool joybus_get_controller_info (int port, joybus_controller_info_t *info, bool reset) {
|
||||
joybus_cmd_info_t cmd = { .info = {
|
||||
.cmd = reset ? JOYBUS_CMD_RESET : JOYBUS_CMD_INFO,
|
||||
.rx = { .length = sizeof(cmd.rx) }
|
||||
} };
|
||||
|
||||
if (joybus_execute_command(port, &cmd, sizeof(cmd))) {
|
||||
joybus_copy_controller_info(&cmd, info);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool joybus_get_controller_state (int port, joybus_controller_state_t *state) {
|
||||
joybus_cmd_state_t cmd = { .info = {
|
||||
.cmd = JOYBUS_CMD_STATE,
|
||||
.rx = { .length = sizeof(cmd.rx) }
|
||||
} };
|
||||
|
||||
if (joybus_execute_command(port, &cmd, sizeof(cmd))) {
|
||||
joybus_copy_controller_state(&cmd, state);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
39
sw/bootloader/src/joybus.h
Normal file
39
sw/bootloader/src/joybus.h
Normal file
@ -0,0 +1,39 @@
|
||||
#ifndef JOYBUS_H__
|
||||
#define JOYBUS_H__
|
||||
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint16_t id;
|
||||
uint8_t flags;
|
||||
} joybus_controller_info_t;
|
||||
|
||||
typedef struct {
|
||||
bool a;
|
||||
bool b;
|
||||
bool z;
|
||||
bool start;
|
||||
bool up;
|
||||
bool down;
|
||||
bool left;
|
||||
bool right;
|
||||
bool reset;
|
||||
bool l;
|
||||
bool r;
|
||||
bool c_up;
|
||||
bool c_down;
|
||||
bool c_left;
|
||||
bool c_right;
|
||||
int8_t x;
|
||||
int8_t y;
|
||||
} joybus_controller_state_t;
|
||||
|
||||
|
||||
bool joybus_get_controller_info (int port, joybus_controller_info_t *info, bool reset);
|
||||
bool joybus_get_controller_state (int port, joybus_controller_state_t *state);
|
||||
|
||||
|
||||
#endif
|
@ -3,6 +3,7 @@
|
||||
#include "error.h"
|
||||
#include "init.h"
|
||||
#include "io.h"
|
||||
#include "joybus.h"
|
||||
#include "menu.h"
|
||||
#include "sc64.h"
|
||||
|
||||
@ -22,6 +23,20 @@ void main (void) {
|
||||
boot_params.cic_seed = (sc64_boot_params.cic_seed & 0xFF);
|
||||
boot_params.detect_cic_seed = (sc64_boot_params.cic_seed == CIC_SEED_AUTO);
|
||||
|
||||
switch (sc64_boot_params.boot_mode) {
|
||||
case BOOT_MODE_ROM:
|
||||
case BOOT_MODE_DDIPL: {
|
||||
joybus_controller_state_t controller_state;
|
||||
if (joybus_get_controller_state(0, &controller_state) && controller_state.r) {
|
||||
sc64_boot_params.boot_mode = BOOT_MODE_MENU;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (sc64_boot_params.boot_mode) {
|
||||
case BOOT_MODE_MENU:
|
||||
menu_load();
|
||||
|
@ -15,49 +15,61 @@ static const char *fatfs_error_codes[] = {
|
||||
"No error",
|
||||
"A hard error occurred in the low level disk I/O layer",
|
||||
"Assertion failed",
|
||||
"The physical drive cannot work",
|
||||
"The physical drive does not work",
|
||||
"Could not find the file",
|
||||
"Could not find the path",
|
||||
"The path name format is invalid",
|
||||
"Access denied due to prohibited access or directory full",
|
||||
"Access denied due to prohibited access",
|
||||
"Access denied due to a prohibited access or directory full",
|
||||
"Access denied due to a prohibited access",
|
||||
"The file/directory object is invalid",
|
||||
"The physical drive is write protected",
|
||||
"The logical drive number is invalid",
|
||||
"The volume has no work area",
|
||||
"There is no valid FAT volume",
|
||||
"The f_mkfs() aborted due to any problem",
|
||||
"Could not get a grant to access the volume within defined period",
|
||||
"Could not find a valid FAT volume",
|
||||
"The f_mkfs function aborted due to some problem",
|
||||
"Could not take control of the volume within defined period",
|
||||
"The operation is rejected according to the file sharing policy",
|
||||
"LFN working buffer could not be allocated",
|
||||
"LFN working buffer could not be allocated or given buffer is insufficient in size",
|
||||
"Number of open files > FF_FS_LOCK",
|
||||
"Given parameter is invalid",
|
||||
};
|
||||
|
||||
|
||||
#define FF_CHECK(x, message, ...) { \
|
||||
fresult = x; \
|
||||
if (fresult != FR_OK) { \
|
||||
error_display( \
|
||||
message "\n" \
|
||||
" > FatFs error: %s\n" \
|
||||
" > SD card error: %s (%08X)", \
|
||||
__VA_ARGS__ __VA_OPT__(,) \
|
||||
fatfs_error_codes[fresult], \
|
||||
sc64_error_description(sc64_error_fatfs), sc64_error_fatfs \
|
||||
); \
|
||||
} \
|
||||
static void menu_fix_file_size (FIL *fil) {
|
||||
fil->obj.objsize = ALIGN(f_size(fil), FF_MAX_SS);
|
||||
}
|
||||
|
||||
static void menu_error_display (const char *message, FRESULT fresult) {
|
||||
error_display(
|
||||
" > FatFs error: %s\n"
|
||||
" > SD card error: %s (%08X)\n"
|
||||
"\n"
|
||||
"[ %s ]\n"
|
||||
" > Please insert a FAT32/exFAT formatted SD card.\n"
|
||||
" > To start the menu please put \"sc64menu.n64\" file\n"
|
||||
" in the top directory on the SD card.\n"
|
||||
" > Latest menu version is available on the\n"
|
||||
" https://menu.summercart64.dev website.\n",
|
||||
fatfs_error_codes[fresult],
|
||||
sc64_error_description(sc64_error_fatfs),
|
||||
sc64_error_fatfs,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
static void fix_menu_file_size (FIL *fil) {
|
||||
fil->obj.objsize = ALIGN(f_size(fil), FF_MAX_SS);
|
||||
#define FF_CHECK(x, message) { \
|
||||
fresult = x; \
|
||||
if (fresult != FR_OK) { \
|
||||
menu_error_display(message, fresult); \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
void menu_load (void) {
|
||||
sc64_error_t error;
|
||||
bool writeback_pending;
|
||||
sc64_sd_card_status_t sd_card_status;
|
||||
FRESULT fresult;
|
||||
FATFS fs;
|
||||
FIL fil;
|
||||
@ -73,9 +85,17 @@ void menu_load (void) {
|
||||
error_display("Could not disable save writeback\n (%08X) - %s", error, sc64_error_description(error));
|
||||
}
|
||||
|
||||
if ((error = sc64_sd_card_get_status(&sd_card_status)) != SC64_OK) {
|
||||
error_display("Could not get SD card status\n (%08X) - %s", error, sc64_error_description(error));
|
||||
}
|
||||
|
||||
if (!(sd_card_status & SD_CARD_STATUS_INSERTED)) {
|
||||
menu_error_display("No SD card detected in the slot", FR_OK);
|
||||
}
|
||||
|
||||
FF_CHECK(f_mount(&fs, "", 1), "SD card initialize error");
|
||||
FF_CHECK(f_open(&fil, "sc64menu.n64", FA_READ), "Could not open menu executable (sc64menu.n64)");
|
||||
fix_menu_file_size(&fil);
|
||||
FF_CHECK(f_open(&fil, "sc64menu.n64", FA_READ), "Could not open menu executable");
|
||||
menu_fix_file_size(&fil);
|
||||
FF_CHECK(f_read(&fil, (void *) (ROM_ADDRESS), f_size(&fil), &bytes_read), "Could not read menu file");
|
||||
FF_CHECK((bytes_read != f_size(&fil)) ? FR_INT_ERR : FR_OK, "Read size is different than expected");
|
||||
FF_CHECK(f_close(&fil), "Could not close menu file");
|
||||
|
@ -194,6 +194,7 @@ const char *sc64_error_description (sc64_error_t error) {
|
||||
case SD_ERROR_ACMD41_IO: return "ACMD41 I/O";
|
||||
case SD_ERROR_ACMD41_OCR: return "ACMD41 OCR";
|
||||
case SD_ERROR_ACMD41_TIMEOUT: return "ACMD41 timeout";
|
||||
case SD_ERROR_LOCKED: return "SD card is locked by the PC side";
|
||||
default: return "Unknown error (SD)";
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ typedef enum {
|
||||
SD_ERROR_ACMD41_IO = 27,
|
||||
SD_ERROR_ACMD41_OCR = 28,
|
||||
SD_ERROR_ACMD41_TIMEOUT = 29,
|
||||
SD_ERROR_LOCKED = 30,
|
||||
} sc64_sd_error_t;
|
||||
|
||||
typedef uint32_t sc64_error_t;
|
||||
@ -103,7 +104,8 @@ typedef enum {
|
||||
SAVE_TYPE_SRAM = 3,
|
||||
SAVE_TYPE_FLASHRAM = 4,
|
||||
SAVE_TYPE_SRAM_BANKED = 5,
|
||||
SAVE_TYPE_SRAM_1M = 6
|
||||
SAVE_TYPE_SRAM_1M = 6,
|
||||
SAVE_TYPE_FLASHRAM_FAKE = 7,
|
||||
} sc64_save_type_t;
|
||||
|
||||
typedef enum {
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "fatfs/ff.h"
|
||||
#include "init.h"
|
||||
#include "io.h"
|
||||
#include "joybus.h"
|
||||
#include "sc64.h"
|
||||
#include "test.h"
|
||||
|
||||
@ -55,6 +56,7 @@ static void fill_random (uint32_t *buffer, int size, uint32_t pattern, uint32_t
|
||||
static void test_sc64_cfg (void) {
|
||||
sc64_error_t error;
|
||||
uint32_t button_state;
|
||||
joybus_controller_info_t controller_info;
|
||||
uint32_t identifier;
|
||||
uint16_t major;
|
||||
uint16_t minor;
|
||||
@ -71,6 +73,10 @@ static void test_sc64_cfg (void) {
|
||||
|
||||
display_printf("done\n\n");
|
||||
|
||||
if (joybus_get_controller_info(0, &controller_info, false)) {
|
||||
display_printf("Found controller on port 0: 0x%04X | 0x%02X\n\n", controller_info.id, controller_info.flags);
|
||||
}
|
||||
|
||||
if ((error = sc64_get_identifier(&identifier)) != SC64_OK) {
|
||||
error_display("Command IDENTIFIER_GET failed\n (%08X) - %s", error, sc64_error_description(error));
|
||||
}
|
||||
@ -178,7 +184,7 @@ static void test_sd_card_io (void) {
|
||||
uint8_t sector[512] __attribute__((aligned(8)));
|
||||
|
||||
if ((error = sc64_sd_card_get_status(&card_status)) != SC64_OK) {
|
||||
error_display("Could not get SD card info\n (%08X) - %s", error, sc64_error_description(error));
|
||||
error_display("Could not get SD card status\n (%08X) - %s", error, sc64_error_description(error));
|
||||
}
|
||||
|
||||
if (card_status & SD_CARD_STATUS_INSERTED) {
|
||||
@ -188,11 +194,11 @@ static void test_sd_card_io (void) {
|
||||
}
|
||||
|
||||
if ((error = sc64_sd_card_deinit()) != SC64_OK) {
|
||||
error_display("SD card deinit error\n (%08X) - %s", error, sc64_error_description(error));
|
||||
return display_printf("SD card deinit error, skipping test\n (%08X) - %s", error, sc64_error_description(error));
|
||||
}
|
||||
|
||||
if ((error = sc64_sd_card_init()) != SC64_OK) {
|
||||
return display_printf("SD card init error\n (%08X) - %s\n", error, sc64_error_description(error));
|
||||
return display_printf("SD card init error, skipping test\n (%08X) - %s\n", error, sc64_error_description(error));
|
||||
}
|
||||
|
||||
if ((error = sc64_sd_card_get_status(&card_status)) != SC64_OK) {
|
||||
@ -261,7 +267,7 @@ static void test_sd_card_fatfs (void) {
|
||||
UINT bytes;
|
||||
|
||||
if ((error = sc64_sd_card_deinit()) != SC64_OK) {
|
||||
error_display("SD card deinit error\n (%08X) - %s", error, sc64_error_description(error));
|
||||
return display_printf("SD card deinit error, skipping test\n (%08X) - %s", error, sc64_error_description(error));
|
||||
}
|
||||
|
||||
if ((fresult = f_mount(&fs, "", 1)) != FR_OK) {
|
||||
|
@ -35,9 +35,10 @@ const struct {
|
||||
|
||||
|
||||
void version_print (void) {
|
||||
display_printf("[ SC64 bootloader metadata ]\n");
|
||||
display_printf("branch: %s | tag: %s\n", version.git_branch, version.git_tag);
|
||||
display_printf("[ SC64 btldr ] %s | %s\n", version.git_branch, version.git_tag);
|
||||
display_printf("sha: %s\n", version.git_sha);
|
||||
display_printf("msg: %s\n", version.git_message);
|
||||
display_printf("\n");
|
||||
display_printf("Copyright 2020 - 2025 Mateusz Faderewski\n");
|
||||
display_printf("Licensed under | FW/SW: GPL-3.0 | HW: CERN-OHL-S-2.0\n");
|
||||
display_printf("Official website: https://summercart64.dev\n\n");
|
||||
}
|
||||
|
@ -92,15 +92,6 @@ typedef enum {
|
||||
TV_TYPE_PASSTHROUGH = 3
|
||||
} tv_type_t;
|
||||
|
||||
typedef enum {
|
||||
SD_CARD_OP_DEINIT = 0,
|
||||
SD_CARD_OP_INIT = 1,
|
||||
SD_CARD_OP_GET_STATUS = 2,
|
||||
SD_CARD_OP_GET_INFO = 3,
|
||||
SD_CARD_OP_BYTE_SWAP_ON = 4,
|
||||
SD_CARD_OP_BYTE_SWAP_OFF = 5,
|
||||
} sd_card_op_t;
|
||||
|
||||
typedef enum {
|
||||
DIAGNOSTIC_ID_VOLTAGE_TEMPERATURE = 0,
|
||||
} diagnostic_id_t;
|
||||
@ -141,6 +132,10 @@ static struct process p;
|
||||
|
||||
|
||||
static bool cfg_cmd_check (void) {
|
||||
if (!writeback_pending() && !hw_gpio_get(GPIO_ID_N64_RESET)) {
|
||||
sd_release_lock(SD_LOCK_N64);
|
||||
}
|
||||
|
||||
uint32_t reg = fpga_reg_get(REG_CFG_CMD);
|
||||
|
||||
if (reg & CFG_CMD_AUX_PENDING) {
|
||||
@ -283,6 +278,9 @@ static bool cfg_set_save_type (save_type_t save_type) {
|
||||
case SAVE_TYPE_SRAM_1M:
|
||||
cfg_change_scr_bits(CFG_SCR_SRAM_ENABLED, true);
|
||||
break;
|
||||
case SAVE_TYPE_FLASHRAM_FAKE:
|
||||
cfg_change_scr_bits(CFG_SCR_FLASHRAM_ENABLED, true);
|
||||
break;
|
||||
default:
|
||||
save_type = SAVE_TYPE_NONE;
|
||||
break;
|
||||
@ -634,60 +632,75 @@ void cfg_process (void) {
|
||||
p.data[0] = p.usb_output_ready ? 0 : (1 << 31);
|
||||
break;
|
||||
|
||||
case CMD_ID_SD_CARD_OP:
|
||||
case CMD_ID_SD_CARD_OP: {
|
||||
sd_error_t error = SD_OK;
|
||||
switch (p.data[1]) {
|
||||
case SD_CARD_OP_DEINIT:
|
||||
sd_card_deinit();
|
||||
break;
|
||||
|
||||
case SD_CARD_OP_INIT: {
|
||||
led_activity_on();
|
||||
sd_error_t error = sd_card_init();
|
||||
led_activity_off();
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
case SD_OP_DEINIT:
|
||||
error = sd_try_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
sd_card_deinit();
|
||||
sd_release_lock(SD_LOCK_N64);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SD_CARD_OP_GET_STATUS:
|
||||
case SD_OP_INIT:
|
||||
error = sd_try_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
led_activity_on();
|
||||
error = sd_card_init();
|
||||
led_activity_off();
|
||||
if (error != SD_OK) {
|
||||
sd_release_lock(SD_LOCK_N64);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SD_OP_GET_STATUS:
|
||||
p.data[1] = sd_card_get_status();
|
||||
break;
|
||||
|
||||
case SD_CARD_OP_GET_INFO:
|
||||
case SD_OP_GET_INFO:
|
||||
if (cfg_translate_address(&p.data[0], SD_CARD_INFO_SIZE, (SDRAM | BRAM))) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||
}
|
||||
sd_error_t error = sd_card_get_info(p.data[0]);
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
error = sd_card_get_info(p.data[0]);
|
||||
}
|
||||
break;
|
||||
|
||||
case SD_CARD_OP_BYTE_SWAP_ON: {
|
||||
sd_error_t error = sd_set_byte_swap(true);
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
case SD_OP_BYTE_SWAP_ON:
|
||||
error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
error = sd_set_byte_swap(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SD_CARD_OP_BYTE_SWAP_OFF: {
|
||||
sd_error_t error = sd_set_byte_swap(false);
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
case SD_OP_BYTE_SWAP_OFF:
|
||||
error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
error = sd_set_byte_swap(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_OPERATION);
|
||||
error = SD_ERROR_INVALID_OPERATION;
|
||||
break;
|
||||
}
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_ID_SD_SECTOR_SET:
|
||||
case CMD_ID_SD_SECTOR_SET: {
|
||||
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
}
|
||||
p.sd_card_sector = p.data[0];
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_ID_SD_READ: {
|
||||
if (p.data[1] >= 0x800000) {
|
||||
@ -696,9 +709,12 @@ void cfg_process (void) {
|
||||
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||
}
|
||||
led_activity_on();
|
||||
sd_error_t error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||
led_activity_off();
|
||||
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
led_activity_on();
|
||||
error = sd_read_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||
led_activity_off();
|
||||
}
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
}
|
||||
@ -713,9 +729,12 @@ void cfg_process (void) {
|
||||
if (cfg_translate_address(&p.data[0], (p.data[1] * SD_SECTOR_SIZE), (SDRAM | FLASH | BRAM))) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, SD_ERROR_INVALID_ADDRESS);
|
||||
}
|
||||
led_activity_on();
|
||||
sd_error_t error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||
led_activity_off();
|
||||
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
led_activity_on();
|
||||
error = sd_write_sectors(p.data[0], p.sd_card_sector, p.data[1]);
|
||||
led_activity_off();
|
||||
}
|
||||
if (error != SD_OK) {
|
||||
return cfg_cmd_reply_error(ERROR_TYPE_SD_CARD, error);
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ typedef enum {
|
||||
SAVE_TYPE_FLASHRAM = 4,
|
||||
SAVE_TYPE_SRAM_BANKED = 5,
|
||||
SAVE_TYPE_SRAM_1M = 6,
|
||||
SAVE_TYPE_FLASHRAM_FAKE = 7,
|
||||
__SAVE_TYPE_COUNT
|
||||
} save_type_t;
|
||||
|
||||
|
@ -134,11 +134,14 @@ static bool dd_block_read_request (void) {
|
||||
uint16_t index = dd_track_head_block();
|
||||
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
||||
if (p.sd_mode) {
|
||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, false);
|
||||
led_activity_on();
|
||||
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
|
||||
led_activity_off();
|
||||
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, false);
|
||||
led_activity_on();
|
||||
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_read_sectors);
|
||||
led_activity_off();
|
||||
}
|
||||
dd_set_block_ready(error == SD_OK);
|
||||
} else {
|
||||
usb_tx_info_t packet_info;
|
||||
@ -158,11 +161,14 @@ static bool dd_block_write_request (void) {
|
||||
uint32_t index = dd_track_head_block();
|
||||
uint32_t buffer_address = DD_BLOCK_BUFFER_ADDRESS;
|
||||
if (p.sd_mode) {
|
||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, true);
|
||||
led_activity_on();
|
||||
sd_error_t error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
|
||||
led_activity_off();
|
||||
sd_error_t error = sd_get_lock(SD_LOCK_N64);
|
||||
if (error == SD_OK) {
|
||||
uint32_t sector_table[DD_SD_SECTOR_TABLE_SIZE];
|
||||
uint32_t sectors = dd_fill_sd_sector_table(index, sector_table, true);
|
||||
led_activity_on();
|
||||
error = sd_optimize_sectors(buffer_address, sector_table, sectors, sd_write_sectors);
|
||||
led_activity_off();
|
||||
}
|
||||
dd_set_block_ready(error == SD_OK);
|
||||
} else {
|
||||
usb_tx_info_t packet_info;
|
||||
|
@ -1,12 +1,18 @@
|
||||
#include <stdint.h>
|
||||
#include "cfg.h"
|
||||
#include "fpga.h"
|
||||
#include "hw.h"
|
||||
#include "timer.h"
|
||||
|
||||
|
||||
#define FLASHRAM_SIZE (128 * 1024)
|
||||
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
|
||||
#define FLASHRAM_PAGE_SIZE (128)
|
||||
#define FLASHRAM_ADDRESS (0x03FE0000UL)
|
||||
#define FLASHRAM_BUFFER_ADDRESS (0x05002900UL)
|
||||
#define FLASHRAM_SIZE (128 * 1024)
|
||||
#define FLASHRAM_SECTOR_SIZE (16 * 1024)
|
||||
#define FLASHRAM_PAGE_SIZE (128)
|
||||
#define FLASHRAM_ADDRESS (0x03FE0000UL)
|
||||
#define FLASHRAM_BUFFER_ADDRESS (0x05002C00UL)
|
||||
|
||||
#define FLASHRAM_WRITE_TIMING_MS (3)
|
||||
#define FLASHRAM_ERASE_TIMING_MS (200)
|
||||
|
||||
|
||||
typedef enum {
|
||||
@ -16,6 +22,13 @@ typedef enum {
|
||||
OP_WRITE_PAGE
|
||||
} flashram_op_t;
|
||||
|
||||
struct process {
|
||||
bool pending;
|
||||
};
|
||||
|
||||
|
||||
static struct process p;
|
||||
|
||||
|
||||
static flashram_op_t flashram_operation_type (uint32_t scr) {
|
||||
if (!(scr & FLASHRAM_SCR_PENDING)) {
|
||||
@ -38,6 +51,7 @@ void flashram_init (void) {
|
||||
if (fpga_reg_get(REG_FLASHRAM_SCR) & FLASHRAM_SCR_PENDING) {
|
||||
fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE);
|
||||
}
|
||||
p.pending = false;
|
||||
}
|
||||
|
||||
|
||||
@ -53,8 +67,15 @@ void flashram_process (void) {
|
||||
uint8_t write_buffer[FLASHRAM_PAGE_SIZE];
|
||||
|
||||
uint32_t page = ((scr & FLASHRAM_SCR_PAGE_MASK) >> FLASHRAM_SCR_PAGE_BIT);
|
||||
const bool full_emulation = (cfg_get_save_type() != SAVE_TYPE_FLASHRAM_FAKE);
|
||||
|
||||
if (op == OP_WRITE_PAGE) {
|
||||
if (p.pending) {
|
||||
if (timer_countdown_elapsed(TIMER_ID_FLASHRAM)) {
|
||||
p.pending = false;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (op == OP_WRITE_PAGE) {
|
||||
uint8_t page_buffer[FLASHRAM_PAGE_SIZE];
|
||||
|
||||
uint32_t address = (FLASHRAM_ADDRESS + (page * FLASHRAM_PAGE_SIZE));
|
||||
@ -63,11 +84,24 @@ void flashram_process (void) {
|
||||
fpga_mem_read(address, FLASHRAM_PAGE_SIZE, write_buffer);
|
||||
|
||||
for (int i = 0; i < FLASHRAM_PAGE_SIZE; i++) {
|
||||
write_buffer[i] &= page_buffer[i];
|
||||
if (full_emulation) {
|
||||
write_buffer[i] &= page_buffer[i];
|
||||
} else {
|
||||
write_buffer[i] = page_buffer[i];
|
||||
}
|
||||
}
|
||||
|
||||
fpga_mem_write(address, FLASHRAM_PAGE_SIZE, write_buffer);
|
||||
} else if ((op == OP_ERASE_SECTOR) || (op == OP_ERASE_ALL)) {
|
||||
|
||||
if (full_emulation) {
|
||||
hw_delay_ms(FLASHRAM_WRITE_TIMING_MS);
|
||||
}
|
||||
} else if ((op == OP_ERASE_SECTOR) || (op == OP_ERASE_ALL)) {
|
||||
if (full_emulation) {
|
||||
p.pending = true;
|
||||
timer_countdown_start(TIMER_ID_FLASHRAM, FLASHRAM_ERASE_TIMING_MS);
|
||||
}
|
||||
|
||||
for (int i = 0; i < FLASHRAM_PAGE_SIZE; i++) {
|
||||
write_buffer[i] = 0xFF;
|
||||
}
|
||||
@ -80,6 +114,10 @@ void flashram_process (void) {
|
||||
for (uint32_t offset = 0; offset < erase_size; offset += FLASHRAM_PAGE_SIZE) {
|
||||
fpga_mem_write(address + offset, FLASHRAM_PAGE_SIZE, write_buffer);
|
||||
}
|
||||
|
||||
if (full_emulation) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fpga_reg_set(REG_FLASHRAM_SCR, FLASHRAM_SCR_DONE);
|
||||
|
@ -4,7 +4,7 @@
|
||||
#include "timer.h"
|
||||
|
||||
|
||||
#define SD_INIT_BUFFER_ADDRESS (0x05002800UL)
|
||||
#define SD_INIT_BUFFER_ADDRESS (0x05002A00UL)
|
||||
#define BYTE_SWAP_ADDRESS_END (0x05000000UL)
|
||||
|
||||
#define CMD6_ARG_CHECK_HS (0x00FFFFF1UL)
|
||||
@ -35,11 +35,10 @@
|
||||
#define R7_CHECK_PATTERN (0xAA << 0)
|
||||
|
||||
#define TIMEOUT_INIT_MS (1000)
|
||||
#define TIMEOUT_DATA_MS (5000)
|
||||
|
||||
#define DAT_CRC16_LENGTH (8)
|
||||
#define DAT_BLOCK_MAX_COUNT (256)
|
||||
#define DAT_TIMEOUT_INIT_MS (2000)
|
||||
#define DAT_TIMEOUT_DATA_MS (5000)
|
||||
|
||||
|
||||
typedef enum {
|
||||
@ -59,16 +58,12 @@ typedef enum {
|
||||
RSP_R7,
|
||||
} rsp_type_t;
|
||||
|
||||
typedef enum {
|
||||
DAT_READ,
|
||||
DAT_WRITE,
|
||||
} dat_mode_t;
|
||||
|
||||
typedef enum {
|
||||
DAT_OK,
|
||||
DAT_BUSY,
|
||||
DAT_ERROR_IO,
|
||||
DAT_ERROR_TIMEOUT,
|
||||
} dat_error_t;
|
||||
} dat_status_t;
|
||||
|
||||
typedef enum {
|
||||
CMD6_OK,
|
||||
@ -86,6 +81,7 @@ struct process {
|
||||
uint8_t csd[16];
|
||||
uint8_t cid[16];
|
||||
bool byte_swap;
|
||||
sd_lock_t lock;
|
||||
};
|
||||
|
||||
|
||||
@ -110,6 +106,7 @@ static void sd_set_clock (sd_clock_t mode) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool sd_cmd (uint8_t cmd, uint32_t arg, rsp_type_t rsp_type, void *rsp) {
|
||||
uint32_t scr;
|
||||
uint32_t cmd_data;
|
||||
@ -175,52 +172,109 @@ static bool sd_acmd (uint8_t acmd, uint32_t arg, rsp_type_t rsp_type, void *rsp)
|
||||
return false;
|
||||
}
|
||||
|
||||
static void sd_dat_prepare (uint32_t address, uint32_t count, dat_mode_t mode) {
|
||||
uint32_t length = (count * SD_SECTOR_SIZE);
|
||||
uint32_t sd_dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_FIFO_FLUSH);
|
||||
uint32_t sd_dma_scr = DMA_SCR_START;
|
||||
|
||||
if (mode == DAT_READ) {
|
||||
sd_dat |= SD_DAT_START_READ;
|
||||
sd_dma_scr |= DMA_SCR_DIRECTION;
|
||||
if (p.byte_swap && (address < BYTE_SWAP_ADDRESS_END)) {
|
||||
sd_dma_scr |= DMA_SCR_BYTE_SWAP;
|
||||
}
|
||||
} else {
|
||||
sd_dat |= SD_DAT_START_WRITE;
|
||||
static void sd_dat_start_write (uint32_t count) {
|
||||
uint32_t dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_START_WRITE | SD_DAT_FIFO_FLUSH);
|
||||
fpga_reg_set(REG_SD_DAT, dat);
|
||||
}
|
||||
|
||||
static void sd_dat_start_read (uint32_t count) {
|
||||
uint32_t dat = (((count - 1) << SD_DAT_BLOCKS_BIT) | SD_DAT_START_READ | SD_DAT_FIFO_FLUSH);
|
||||
fpga_reg_set(REG_SD_DAT, dat);
|
||||
}
|
||||
|
||||
static dat_status_t sd_dat_status (void) {
|
||||
uint32_t dat = fpga_reg_get(REG_SD_DAT);
|
||||
if (dat & SD_DAT_BUSY) {
|
||||
return DAT_BUSY;
|
||||
}
|
||||
|
||||
fpga_reg_set(REG_SD_DAT, sd_dat);
|
||||
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
|
||||
fpga_reg_set(REG_SD_DMA_LENGTH, length);
|
||||
fpga_reg_set(REG_SD_DMA_SCR, sd_dma_scr);
|
||||
if (dat & SD_DAT_ERROR) {
|
||||
return DAT_ERROR_IO;
|
||||
}
|
||||
return DAT_OK;
|
||||
}
|
||||
|
||||
static void sd_dat_abort (void) {
|
||||
fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP);
|
||||
fpga_reg_set(REG_SD_DAT, SD_DAT_STOP | SD_DAT_FIFO_FLUSH);
|
||||
}
|
||||
|
||||
static dat_error_t sd_dat_wait (uint16_t timeout_ms) {
|
||||
|
||||
static void sd_dma_start_write (uint32_t address, uint32_t count) {
|
||||
uint32_t length = (count * SD_SECTOR_SIZE);
|
||||
uint32_t scr = DMA_SCR_START;
|
||||
|
||||
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
|
||||
fpga_reg_set(REG_SD_DMA_LENGTH, length);
|
||||
fpga_reg_set(REG_SD_DMA_SCR, scr);
|
||||
}
|
||||
|
||||
static void sd_dma_start_read (uint32_t address, uint32_t count) {
|
||||
uint32_t length = (count * SD_SECTOR_SIZE);
|
||||
uint32_t scr = (DMA_SCR_DIRECTION | DMA_SCR_START);
|
||||
|
||||
if (p.byte_swap && (address < BYTE_SWAP_ADDRESS_END)) {
|
||||
scr |= DMA_SCR_BYTE_SWAP;
|
||||
}
|
||||
|
||||
fpga_reg_set(REG_SD_DMA_ADDRESS, address);
|
||||
fpga_reg_set(REG_SD_DMA_LENGTH, length);
|
||||
fpga_reg_set(REG_SD_DMA_SCR, scr);
|
||||
}
|
||||
|
||||
static bool sd_dma_is_busy (void) {
|
||||
return (fpga_reg_get(REG_SD_DMA_SCR) & DMA_SCR_BUSY);
|
||||
}
|
||||
|
||||
static void sd_dma_abort (void) {
|
||||
fpga_reg_set(REG_SD_DMA_SCR, DMA_SCR_STOP);
|
||||
while (sd_dma_is_busy());
|
||||
}
|
||||
|
||||
|
||||
static void sd_start_write (uint32_t address, uint32_t count) {
|
||||
sd_dat_start_write(count);
|
||||
sd_dma_start_write(address, count);
|
||||
}
|
||||
|
||||
static void sd_start_read (uint32_t address, uint32_t count) {
|
||||
sd_dat_start_read(count);
|
||||
sd_dma_start_read(address, count);
|
||||
}
|
||||
|
||||
static void sd_abort (void) {
|
||||
sd_dma_abort();
|
||||
sd_dat_abort();
|
||||
}
|
||||
|
||||
static dat_status_t sd_sync (uint16_t timeout_ms) {
|
||||
timer_countdown_start(TIMER_ID_SD, timeout_ms);
|
||||
|
||||
do {
|
||||
uint32_t sd_dat = fpga_reg_get(REG_SD_DAT);
|
||||
uint32_t sd_dma_scr = fpga_reg_get(REG_SD_DMA_SCR);
|
||||
if ((!(sd_dat & SD_DAT_BUSY)) && (!(sd_dma_scr & DMA_SCR_BUSY))) {
|
||||
if (sd_dat & SD_DAT_ERROR) {
|
||||
sd_dat_abort();
|
||||
return DAT_ERROR_IO;
|
||||
}
|
||||
return DAT_OK;
|
||||
while (true) {
|
||||
dat_status_t status = sd_dat_status();
|
||||
|
||||
switch (status) {
|
||||
case DAT_BUSY:
|
||||
break;
|
||||
|
||||
case DAT_OK:
|
||||
if (!sd_dma_is_busy()) {
|
||||
return DAT_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
sd_abort();
|
||||
return status;
|
||||
}
|
||||
} while (!timer_countdown_elapsed(TIMER_ID_SD));
|
||||
|
||||
sd_dat_abort();
|
||||
|
||||
return DAT_ERROR_TIMEOUT;
|
||||
if (timer_countdown_elapsed(TIMER_ID_SD)) {
|
||||
sd_abort();
|
||||
return DAT_ERROR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool sd_dat_check_crc16 (uint8_t *data, uint32_t length) {
|
||||
uint16_t device_crc[4];
|
||||
uint16_t controller_crc[4];
|
||||
@ -265,15 +319,15 @@ static bool sd_dat_check_crc16 (uint8_t *data, uint32_t length) {
|
||||
|
||||
static cmd6_error_t sd_cmd6 (uint32_t arg, uint8_t *buffer) {
|
||||
uint32_t rsp;
|
||||
sd_dat_prepare(SD_INIT_BUFFER_ADDRESS, 1, DAT_READ);
|
||||
sd_start_read(SD_INIT_BUFFER_ADDRESS, 1);
|
||||
if (sd_cmd(6, arg, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
sd_abort();
|
||||
if ((!sd_cmd(13, p.rca, RSP_R1, &rsp)) && (rsp & R1_ILLEGAL_COMMAND)) {
|
||||
return CMD6_ERROR_ILLEGAL_CMD;
|
||||
}
|
||||
return CMD6_ERROR_IO;
|
||||
}
|
||||
if (sd_dat_wait(DAT_TIMEOUT_INIT_MS) == DAT_ERROR_TIMEOUT) {
|
||||
if (sd_sync(TIMEOUT_DATA_MS) == DAT_ERROR_TIMEOUT) {
|
||||
return CMD6_ERROR_TIMEOUT;
|
||||
}
|
||||
fpga_mem_read(SD_INIT_BUFFER_ADDRESS, CMD6_DATA_LENGTH + DAT_CRC16_LENGTH, buffer);
|
||||
@ -428,6 +482,7 @@ sd_error_t sd_card_init (void) {
|
||||
void sd_card_deinit (void) {
|
||||
if (p.card_initialized) {
|
||||
p.card_initialized = false;
|
||||
p.card_type_block = false;
|
||||
p.byte_swap = false;
|
||||
sd_set_clock(CLOCK_400KHZ);
|
||||
sd_cmd(0, 0, RSP_NONE, NULL);
|
||||
@ -493,13 +548,12 @@ sd_error_t sd_write_sectors (uint32_t address, uint32_t sector, uint32_t count)
|
||||
if (sd_cmd(25, sector, RSP_R1, NULL)) {
|
||||
return SD_ERROR_CMD25_IO;
|
||||
}
|
||||
sd_dat_prepare(address, blocks, DAT_WRITE);
|
||||
dat_error_t error = sd_dat_wait(DAT_TIMEOUT_DATA_MS);
|
||||
if (error != DAT_OK) {
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
return (error == DAT_ERROR_IO) ? SD_ERROR_CMD25_CRC : SD_ERROR_CMD25_TIMEOUT;
|
||||
}
|
||||
sd_start_write(address, blocks);
|
||||
dat_status_t status = sd_sync(TIMEOUT_DATA_MS);
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
if (status != DAT_OK) {
|
||||
return (status == DAT_ERROR_IO) ? SD_ERROR_CMD25_CRC : SD_ERROR_CMD25_TIMEOUT;
|
||||
}
|
||||
address += (blocks * SD_SECTOR_SIZE);
|
||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||
count -= blocks;
|
||||
@ -527,17 +581,16 @@ sd_error_t sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count) {
|
||||
|
||||
while (count > 0) {
|
||||
uint32_t blocks = ((count > DAT_BLOCK_MAX_COUNT) ? DAT_BLOCK_MAX_COUNT : count);
|
||||
sd_dat_prepare(address, blocks, DAT_READ);
|
||||
sd_start_read(address, blocks);
|
||||
if (sd_cmd(18, sector, RSP_R1, NULL)) {
|
||||
sd_dat_abort();
|
||||
sd_abort();
|
||||
return SD_ERROR_CMD18_IO;
|
||||
}
|
||||
dat_error_t error = sd_dat_wait(DAT_TIMEOUT_DATA_MS);
|
||||
if (error != DAT_OK) {
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
return (error == DAT_ERROR_IO) ? SD_ERROR_CMD18_CRC : SD_ERROR_CMD18_TIMEOUT;
|
||||
}
|
||||
dat_status_t status = sd_sync(TIMEOUT_DATA_MS);
|
||||
sd_cmd(12, 0, RSP_R1b, NULL);
|
||||
if (status != DAT_OK) {
|
||||
return (status == DAT_ERROR_IO) ? SD_ERROR_CMD18_CRC : SD_ERROR_CMD18_TIMEOUT;
|
||||
}
|
||||
address += (blocks * SD_SECTOR_SIZE);
|
||||
sector += (blocks * (p.card_type_block ? 1 : SD_SECTOR_SIZE));
|
||||
count -= blocks;
|
||||
@ -576,9 +629,32 @@ sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32
|
||||
}
|
||||
|
||||
|
||||
sd_error_t sd_get_lock (sd_lock_t lock) {
|
||||
if (p.lock == lock) {
|
||||
return SD_OK;
|
||||
}
|
||||
return SD_ERROR_LOCKED;
|
||||
}
|
||||
|
||||
sd_error_t sd_try_lock (sd_lock_t lock) {
|
||||
if (p.lock == SD_LOCK_NONE) {
|
||||
p.lock = lock;
|
||||
return SD_OK;
|
||||
}
|
||||
return sd_get_lock(lock);
|
||||
}
|
||||
|
||||
void sd_release_lock (sd_lock_t lock) {
|
||||
if (p.lock == lock) {
|
||||
p.lock = SD_LOCK_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void sd_init (void) {
|
||||
p.card_initialized = false;
|
||||
p.byte_swap = false;
|
||||
p.lock = SD_LOCK_NONE;
|
||||
sd_set_clock(CLOCK_STOP);
|
||||
}
|
||||
|
||||
|
@ -41,10 +41,26 @@ typedef enum {
|
||||
SD_ERROR_ACMD41_IO = 27,
|
||||
SD_ERROR_ACMD41_OCR = 28,
|
||||
SD_ERROR_ACMD41_TIMEOUT = 29,
|
||||
SD_ERROR_LOCKED = 30,
|
||||
} sd_error_t;
|
||||
|
||||
typedef sd_error_t sd_process_sectors_t (uint32_t address, uint32_t sector, uint32_t count);
|
||||
|
||||
typedef enum {
|
||||
SD_LOCK_NONE,
|
||||
SD_LOCK_N64,
|
||||
SD_LOCK_USB,
|
||||
} sd_lock_t;
|
||||
|
||||
typedef enum {
|
||||
SD_OP_DEINIT = 0,
|
||||
SD_OP_INIT = 1,
|
||||
SD_OP_GET_STATUS = 2,
|
||||
SD_OP_GET_INFO = 3,
|
||||
SD_OP_BYTE_SWAP_ON = 4,
|
||||
SD_OP_BYTE_SWAP_OFF = 5,
|
||||
} sd_op_t;
|
||||
|
||||
|
||||
sd_error_t sd_card_init (void);
|
||||
void sd_card_deinit (void);
|
||||
@ -58,6 +74,10 @@ sd_error_t sd_read_sectors (uint32_t address, uint32_t sector, uint32_t count);
|
||||
|
||||
sd_error_t sd_optimize_sectors (uint32_t address, uint32_t *sector_table, uint32_t count, sd_process_sectors_t sd_process_sectors);
|
||||
|
||||
sd_error_t sd_get_lock (sd_lock_t lock);
|
||||
sd_error_t sd_try_lock (sd_lock_t lock);
|
||||
void sd_release_lock (sd_lock_t lock);
|
||||
|
||||
void sd_init (void);
|
||||
|
||||
void sd_process (void);
|
||||
|
@ -13,6 +13,7 @@ typedef enum {
|
||||
TIMER_ID_SD,
|
||||
TIMER_ID_USB,
|
||||
TIMER_ID_WRITEBACK,
|
||||
TIMER_ID_FLASHRAM,
|
||||
__TIMER_ID_COUNT
|
||||
} timer_id_t;
|
||||
|
||||
|
@ -4,7 +4,9 @@
|
||||
#include "flash.h"
|
||||
#include "fpga.h"
|
||||
#include "hw.h"
|
||||
#include "led.h"
|
||||
#include "rtc.h"
|
||||
#include "sd.h"
|
||||
#include "timer.h"
|
||||
#include "update.h"
|
||||
#include "usb.h"
|
||||
@ -15,7 +17,7 @@
|
||||
#define BOOTLOADER_ADDRESS (0x04E00000UL)
|
||||
#define BOOTLOADER_LENGTH (1920 * 1024)
|
||||
|
||||
#define MEMORY_LENGTH (0x05002980UL)
|
||||
#define MEMORY_LENGTH (0x05002C80UL)
|
||||
|
||||
#define RX_FLUSH_ADDRESS (0x07F00000UL)
|
||||
#define RX_FLUSH_LENGTH (1 * 1024 * 1024)
|
||||
@ -268,6 +270,7 @@ static void usb_rx_process (void) {
|
||||
case 'R':
|
||||
cfg_reset_state();
|
||||
cic_reset_parameters();
|
||||
sd_release_lock(SD_LOCK_USB);
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
break;
|
||||
@ -384,6 +387,122 @@ static void usb_rx_process (void) {
|
||||
p.response_pending = true;
|
||||
break;
|
||||
|
||||
case 'i': {
|
||||
sd_error_t error = SD_OK;
|
||||
switch (p.rx_args[1]) {
|
||||
case SD_OP_DEINIT:
|
||||
error = sd_try_lock(SD_LOCK_USB);
|
||||
if (error == SD_OK) {
|
||||
sd_card_deinit();
|
||||
sd_release_lock(SD_LOCK_USB);
|
||||
}
|
||||
break;
|
||||
|
||||
case SD_OP_INIT:
|
||||
error = sd_try_lock(SD_LOCK_USB);
|
||||
if (error == SD_OK) {
|
||||
led_activity_on();
|
||||
error = sd_card_init();
|
||||
led_activity_off();
|
||||
if (error != SD_OK) {
|
||||
sd_release_lock(SD_LOCK_USB);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SD_OP_GET_STATUS:
|
||||
break;
|
||||
|
||||
case SD_OP_GET_INFO:
|
||||
if (usb_validate_address_length(p.rx_args[0], SD_CARD_INFO_SIZE, true)) {
|
||||
error = SD_ERROR_INVALID_ADDRESS;
|
||||
} else {
|
||||
error = sd_get_lock(SD_LOCK_USB);
|
||||
if (error == SD_OK) {
|
||||
error = sd_card_get_info(p.rx_args[0]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SD_OP_BYTE_SWAP_ON:
|
||||
error = sd_get_lock(SD_LOCK_USB);
|
||||
if (error == SD_OK) {
|
||||
error = sd_set_byte_swap(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case SD_OP_BYTE_SWAP_OFF:
|
||||
error = sd_get_lock(SD_LOCK_USB);
|
||||
if (error == SD_OK) {
|
||||
error = sd_set_byte_swap(false);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
error = SD_ERROR_INVALID_OPERATION;
|
||||
break;
|
||||
}
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
p.response_error = (error != SD_OK);
|
||||
p.response_info.data_length = 8;
|
||||
p.response_info.data[0] = error;
|
||||
p.response_info.data[1] = sd_card_get_status();
|
||||
break;
|
||||
}
|
||||
|
||||
case 's': {
|
||||
uint32_t sector = 0;
|
||||
if (!usb_rx_word(§or)) {
|
||||
break;
|
||||
}
|
||||
sd_error_t error = SD_OK;
|
||||
if (p.rx_args[1] >= 0x800000) {
|
||||
error = SD_ERROR_INVALID_ARGUMENT;
|
||||
} else if (usb_validate_address_length(p.rx_args[0], (p.rx_args[1] * SD_SECTOR_SIZE), true)) {
|
||||
error = SD_ERROR_INVALID_ADDRESS;
|
||||
} else {
|
||||
error = sd_get_lock(SD_LOCK_USB);
|
||||
if (error == SD_OK) {
|
||||
led_activity_on();
|
||||
error = sd_read_sectors(p.rx_args[0], sector, p.rx_args[1]);
|
||||
led_activity_off();
|
||||
}
|
||||
}
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
p.response_error = (error != SD_OK);
|
||||
p.response_info.data_length = 4;
|
||||
p.response_info.data[0] = error;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'S': {
|
||||
uint32_t sector = 0;
|
||||
if (!usb_rx_word(§or)) {
|
||||
break;
|
||||
}
|
||||
sd_error_t error = SD_OK;
|
||||
if (p.rx_args[1] >= 0x800000) {
|
||||
error = SD_ERROR_INVALID_ARGUMENT;
|
||||
} else if (usb_validate_address_length(p.rx_args[0], (p.rx_args[1] * SD_SECTOR_SIZE), true)) {
|
||||
error = SD_ERROR_INVALID_ADDRESS;
|
||||
} else {
|
||||
error = sd_get_lock(SD_LOCK_USB);
|
||||
if (error == SD_OK) {
|
||||
led_activity_on();
|
||||
error = sd_write_sectors(p.rx_args[0], sector, p.rx_args[1]);
|
||||
led_activity_off();
|
||||
}
|
||||
}
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
p.response_pending = true;
|
||||
p.response_error = (error != SD_OK);
|
||||
p.response_info.data_length = 4;
|
||||
p.response_info.data[0] = error;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'D':
|
||||
dd_set_block_ready(p.rx_args[0] == 0);
|
||||
p.rx_state = RX_STATE_IDLE;
|
||||
@ -635,5 +754,6 @@ void usb_process (void) {
|
||||
usb_tx_process();
|
||||
} else {
|
||||
usb_flush_packet();
|
||||
sd_release_lock(SD_LOCK_USB);
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
|
||||
#define VERSION_MAJOR (2)
|
||||
#define VERSION_MINOR (19)
|
||||
#define VERSION_REVISION (0)
|
||||
#define VERSION_MINOR (20)
|
||||
#define VERSION_REVISION (2)
|
||||
|
||||
|
||||
void version_firmware (uint32_t *version, uint32_t *revision) {
|
||||
|
@ -62,6 +62,10 @@ static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *le
|
||||
*address = SRAM_FLASHRAM_ADDRESS;
|
||||
*length = SRAM_1M_LENGTH;
|
||||
break;
|
||||
case SAVE_TYPE_FLASHRAM_FAKE:
|
||||
*address = SRAM_FLASHRAM_ADDRESS;
|
||||
*length = FLASHRAM_LENGTH;
|
||||
break;
|
||||
default:
|
||||
*address = 0;
|
||||
*length = 0;
|
||||
@ -72,19 +76,20 @@ static save_type_t writeback_get_address_length (uint32_t *address, uint32_t *le
|
||||
}
|
||||
|
||||
static void writeback_save_to_sd (void) {
|
||||
save_type_t save;
|
||||
uint32_t address;
|
||||
uint32_t length;
|
||||
|
||||
save = writeback_get_address_length(&address, &length);
|
||||
if (save == SAVE_TYPE_NONE) {
|
||||
if (writeback_get_address_length(&address, &length) == SAVE_TYPE_NONE) {
|
||||
writeback_disable();
|
||||
return;
|
||||
}
|
||||
|
||||
sd_error_t error = sd_optimize_sectors(address, p.sectors, (length / SD_SECTOR_SIZE), sd_write_sectors);
|
||||
if (sd_get_lock(SD_LOCK_N64) != SD_OK) {
|
||||
writeback_disable();
|
||||
return;
|
||||
}
|
||||
|
||||
if (error != SD_OK) {
|
||||
if (sd_optimize_sectors(address, p.sectors, (length / SD_SECTOR_SIZE), sd_write_sectors) != SD_OK) {
|
||||
writeback_disable();
|
||||
return;
|
||||
}
|
||||
|
126
sw/deployer/Cargo.lock
generated
126
sw/deployer/Cargo.lock
generated
@ -124,7 +124,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -162,6 +162,26 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"rustc-hash",
|
||||
"shlex",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.2"
|
||||
@ -218,13 +238,22 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.103"
|
||||
version = "1.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410"
|
||||
checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cexpr"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||
dependencies = [
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -263,6 +292,17 @@ dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.8"
|
||||
@ -303,7 +343,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -482,6 +522,12 @@ dependencies = [
|
||||
"weezl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
@ -633,7 +679,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -755,6 +801,16 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libudev"
|
||||
version = "0.3.0"
|
||||
@ -922,7 +978,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1007,6 +1063,16 @@ version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
@ -1038,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8021cf59c8ec9c432cfc2526ac6b8aa508ecaf29cd415f271b8406c1b851c3fd"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1219,10 +1285,18 @@ dependencies = [
|
||||
"ordered-multimap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "sc64deployer"
|
||||
version = "2.19.0"
|
||||
version = "2.20.2"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"chrono",
|
||||
"clap",
|
||||
"clap-num",
|
||||
@ -1266,14 +1340,14 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.6"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0"
|
||||
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@ -1308,6 +1382,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
@ -1357,9 +1437,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.68"
|
||||
version = "2.0.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1402,7 +1482,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1430,18 +1510,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.6"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.14"
|
||||
version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
@ -1527,7 +1607,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -1549,7 +1629,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
"syn 2.0.77",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -1738,9 +1818,9 @@ checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.13"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
|
||||
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -1,11 +1,15 @@
|
||||
[package]
|
||||
name = "sc64deployer"
|
||||
version = "2.19.0"
|
||||
version = "2.20.2"
|
||||
edition = "2021"
|
||||
authors = ["Polprzewodnikowy"]
|
||||
authors = ["Mateusz Faderewski (Polprzewodnikowy)"]
|
||||
description = "SummerCart64 loader and control software"
|
||||
documentation = "https://github.com/Polprzewodnikowy/SummerCart64"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.70.1"
|
||||
cc = "1.1.18"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.38"
|
||||
clap = { version = "4.5.8", features = ["derive"] }
|
||||
|
21
sw/deployer/build.rs
Normal file
21
sw/deployer/build.rs
Normal file
@ -0,0 +1,21 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("cargo::rerun-if-changed=../bootloader/src/");
|
||||
|
||||
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
||||
cc::Build::new()
|
||||
.file("../bootloader/src/fatfs/ff.c")
|
||||
.file("../bootloader/src/fatfs/ffsystem.c")
|
||||
.file("../bootloader/src/fatfs/ffunicode.c")
|
||||
.compile("fatfs");
|
||||
|
||||
bindgen::Builder::default()
|
||||
.header("../bootloader/src/fatfs/ff.h")
|
||||
.blocklist_function("get_fattime")
|
||||
.generate()
|
||||
.expect("Unable to generate FatFs bindings")
|
||||
.write_to_file(out_dir.join("fatfs_bindings.rs"))
|
||||
.expect("Unable to write FatFs bindings");
|
||||
|
||||
Ok(())
|
||||
}
|
@ -11,16 +11,46 @@ use panic_message::panic_message;
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{stdin, stdout, Read, Write},
|
||||
panic,
|
||||
path::PathBuf,
|
||||
process,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
{panic, process},
|
||||
};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(
|
||||
author,
|
||||
version,
|
||||
about,
|
||||
help_template = "\
|
||||
{before-help}{name} v{version} - {about}
|
||||
Copyright (C) 2020 - 2025 {author}
|
||||
|
||||
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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
Latest information about SummerCart64 is available
|
||||
on the official website: <https://summercart64.dev/>.
|
||||
|
||||
|
||||
{usage-heading} {usage}
|
||||
|
||||
{all-args}{after-help}"
|
||||
)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
@ -57,6 +87,12 @@ enum Commands {
|
||||
/// Dump data from arbitrary location in SC64 memory space
|
||||
Dump(DumpArgs),
|
||||
|
||||
/// Perform operations on the SD card
|
||||
SD {
|
||||
#[command(subcommand)]
|
||||
command: SDCommands,
|
||||
},
|
||||
|
||||
/// Print information about connected SC64 device
|
||||
Info,
|
||||
|
||||
@ -202,6 +238,71 @@ struct DumpArgs {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum SDCommands {
|
||||
/// List a directory on the SD card
|
||||
#[command(name = "ls")]
|
||||
List {
|
||||
/// Path to the directory
|
||||
path: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Display a file or directory status
|
||||
#[command(name = "stat")]
|
||||
Stat {
|
||||
/// Path to the file or directory
|
||||
path: PathBuf,
|
||||
},
|
||||
|
||||
/// Move or rename a file or directory
|
||||
#[command(name = "mv")]
|
||||
Move {
|
||||
/// Path to the current file or directory
|
||||
src: PathBuf,
|
||||
|
||||
/// Path to the new file or directory
|
||||
dst: PathBuf,
|
||||
},
|
||||
|
||||
/// Remove a file or empty directory
|
||||
#[command(name = "rm")]
|
||||
Delete {
|
||||
/// Path to the file or directory
|
||||
path: PathBuf,
|
||||
},
|
||||
|
||||
/// Create a new directory
|
||||
#[command(name = "mkdir")]
|
||||
CreateDirectory {
|
||||
/// Path to the directory
|
||||
path: PathBuf,
|
||||
},
|
||||
|
||||
/// Download a file to the PC
|
||||
#[command(name = "download")]
|
||||
Download {
|
||||
/// Path to the file on the SD card
|
||||
src: PathBuf,
|
||||
|
||||
/// Path to the file on the PC
|
||||
dst: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Upload a file to the SD card
|
||||
#[command(name = "upload")]
|
||||
Upload {
|
||||
/// Path to the file on the PC
|
||||
src: PathBuf,
|
||||
|
||||
/// Path to the file on the SD card
|
||||
dst: Option<PathBuf>,
|
||||
},
|
||||
|
||||
/// Format the SD card
|
||||
#[command(name = "mkfs")]
|
||||
Format,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum SetCommands {
|
||||
/// Synchronize real time clock (RTC) on the SC64 with local system time
|
||||
@ -258,6 +359,7 @@ enum SaveType {
|
||||
SramBanked,
|
||||
Sram1m,
|
||||
Flashram,
|
||||
FlashramFake,
|
||||
}
|
||||
|
||||
impl From<n64::SaveType> for SaveType {
|
||||
@ -284,6 +386,7 @@ impl From<SaveType> for sc64::SaveType {
|
||||
SaveType::SramBanked => Self::SramBanked,
|
||||
SaveType::Sram1m => Self::Sram1m,
|
||||
SaveType::Flashram => Self::Flashram,
|
||||
SaveType::FlashramFake => Self::FlashramFake,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -318,6 +421,9 @@ fn main() {
|
||||
panic::set_hook(Box::new(|_| {}));
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
colored::control::set_virtual_terminal(true).ok();
|
||||
|
||||
match panic::catch_unwind(|| handle_command(&cli.command, cli.port, cli.remote)) {
|
||||
Ok(_) => {}
|
||||
Err(payload) => {
|
||||
@ -340,6 +446,7 @@ fn handle_command(command: &Commands, port: Option<String>, remote: Option<Strin
|
||||
Commands::_64DD(args) => handle_64dd_command(connection, args),
|
||||
Commands::Debug(args) => handle_debug_command(connection, args),
|
||||
Commands::Dump(args) => handle_dump_command(connection, args),
|
||||
Commands::SD { command } => handle_sd_command(connection, command),
|
||||
Commands::Info => handle_info_command(connection),
|
||||
Commands::Reset => handle_reset_command(connection),
|
||||
Commands::Set { command } => handle_set_command(connection, command),
|
||||
@ -423,7 +530,16 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<()
|
||||
sc64.set_tv_type(tv_type)?;
|
||||
}
|
||||
|
||||
sc64.calculate_cic_parameters(args.cic_seed)?;
|
||||
let (seed, checksum, matched) = sc64.calculate_cic_parameters(args.cic_seed)?;
|
||||
if !matched {
|
||||
println!(
|
||||
"{}",
|
||||
"Warning: IPL3 in the ROM does not match any known variant. It may fail to boot."
|
||||
.bright_yellow(),
|
||||
);
|
||||
println!("IPL3 has been automatically signed with the parameters listed below:");
|
||||
println!("[seed = 0x{seed:02X} | checksum = 0x{checksum:012X}]");
|
||||
}
|
||||
|
||||
if args.reboot && !sc64.try_notify_via_aux(sc64::AuxMessage::Reboot)? {
|
||||
println!(
|
||||
@ -538,7 +654,16 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
|
||||
sc64.set_tv_type(tv_type)?;
|
||||
}
|
||||
|
||||
sc64.calculate_cic_parameters(args.cic_seed)?;
|
||||
let (seed, checksum, matched) = sc64.calculate_cic_parameters(args.cic_seed)?;
|
||||
if !matched {
|
||||
println!(
|
||||
"{}",
|
||||
"Warning: IPL3 in the ROM does not match any known variant. It may fail to boot."
|
||||
.bright_yellow(),
|
||||
);
|
||||
println!("IPL3 has been automatically signed with the parameters listed below:");
|
||||
println!("[seed = 0x{seed:02X} | checksum = 0x{checksum:012X}]");
|
||||
}
|
||||
|
||||
if args.disk.len() == 0 {
|
||||
let dd_mode = sc64::DdMode::DdIpl;
|
||||
@ -767,16 +892,167 @@ fn handle_dump_command(connection: Connection, args: &DumpArgs) -> Result<(), sc
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_sd_command(connection: Connection, command: &SDCommands) -> Result<(), sc64::Error> {
|
||||
let mut sc64 = init_sc64(connection, true)?;
|
||||
|
||||
match sc64.init_sd_card()? {
|
||||
sc64::SdCardResult::OK => {}
|
||||
error => {
|
||||
return Err(sc64::Error::new(
|
||||
format!("Couldn't init the SD card: {error}").as_str(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
if sc64.is_console_powered_on()? {
|
||||
println!(
|
||||
"{}\n{}\n{}",
|
||||
"========== [WARNING] ==========".bold().bright_yellow(),
|
||||
"The console is powered on. To avoid potential data corruption it's strongly"
|
||||
.bright_yellow(),
|
||||
"recommended to access the SD card only when the console is powered off."
|
||||
.bright_yellow()
|
||||
);
|
||||
let answer = prompt(format!("{}", "Continue anyways? [y/N] ".bold()));
|
||||
if answer.to_ascii_lowercase() != "y" {
|
||||
sc64.deinit_sd_card()?;
|
||||
println!("{}", "SD card access aborted".red());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
sc64.reset_state()?;
|
||||
|
||||
let mut ff = sc64::ff::FatFs::new(sc64)?;
|
||||
|
||||
match command {
|
||||
SDCommands::List { path } => {
|
||||
for item in ff.list(path.clone().unwrap_or(PathBuf::from("/")))? {
|
||||
let sc64::ff::Entry {
|
||||
info,
|
||||
datetime,
|
||||
name,
|
||||
} = item;
|
||||
let name = match info {
|
||||
sc64::ff::EntryInfo::Directory => ("/".to_owned() + &name).bright_blue(),
|
||||
sc64::ff::EntryInfo::File { size: _ } => name.bright_green(),
|
||||
};
|
||||
println!("{info} {datetime} | {}", name.bold());
|
||||
}
|
||||
}
|
||||
SDCommands::Stat { path } => {
|
||||
let sc64::ff::Entry {
|
||||
info,
|
||||
datetime,
|
||||
name,
|
||||
} = ff.stat(path)?;
|
||||
let name = match info {
|
||||
sc64::ff::EntryInfo::Directory => ("/".to_owned() + &name).bright_blue(),
|
||||
sc64::ff::EntryInfo::File { size: _ } => name.bright_green(),
|
||||
};
|
||||
println!("{info} {datetime} | {}", name.bold());
|
||||
}
|
||||
SDCommands::Move { src, dst } => {
|
||||
ff.rename(src, dst)?;
|
||||
println!(
|
||||
"Successfully moved {} to {}",
|
||||
src.to_str().unwrap_or_default().bright_green(),
|
||||
dst.to_str().unwrap_or_default().bright_green()
|
||||
);
|
||||
}
|
||||
SDCommands::Delete { path } => {
|
||||
ff.delete(path)?;
|
||||
println!(
|
||||
"Successfully deleted {}",
|
||||
path.to_str().unwrap_or_default().bright_green()
|
||||
);
|
||||
}
|
||||
SDCommands::CreateDirectory { path } => {
|
||||
ff.mkdir(path)?;
|
||||
println!(
|
||||
"Successfully created {}",
|
||||
path.to_str().unwrap_or_default().bright_green()
|
||||
);
|
||||
}
|
||||
SDCommands::Download { src, dst } => {
|
||||
let dst = &dst.clone().unwrap_or(
|
||||
src.file_name()
|
||||
.map(PathBuf::from)
|
||||
.ok_or(sc64::ff::Error::InvalidParameter)?,
|
||||
);
|
||||
let mut src_file = ff.open(src)?;
|
||||
let mut dst_file = std::fs::File::create(dst)?;
|
||||
let mut buffer = vec![0; 128 * 1024];
|
||||
log_wait(
|
||||
format!(
|
||||
"Downloading {} to {}",
|
||||
src.to_str().unwrap_or_default().bright_green(),
|
||||
dst.to_str().unwrap_or_default().bright_green()
|
||||
),
|
||||
|| loop {
|
||||
match src_file.read(&mut buffer)? {
|
||||
0 => return Ok(()),
|
||||
bytes => {
|
||||
if let Err(e) = dst_file.write_all(&buffer[0..bytes]) {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)?;
|
||||
}
|
||||
SDCommands::Upload { src, dst } => {
|
||||
let dst = &dst.clone().unwrap_or(
|
||||
src.file_name()
|
||||
.map(PathBuf::from)
|
||||
.ok_or(sc64::ff::Error::InvalidParameter)?,
|
||||
);
|
||||
let mut src_file = std::fs::File::open(src)?;
|
||||
let mut dst_file = ff.create(dst)?;
|
||||
let mut buffer = vec![0; 128 * 1024];
|
||||
log_wait(
|
||||
format!(
|
||||
"Uploading {} to {}",
|
||||
src.to_str().unwrap_or_default().bright_green(),
|
||||
dst.to_str().unwrap_or_default().bright_green()
|
||||
),
|
||||
|| loop {
|
||||
match src_file.read(&mut buffer)? {
|
||||
0 => return Ok(()),
|
||||
bytes => {
|
||||
if let Err(e) = dst_file.write_all(&buffer[0..bytes]) {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)?;
|
||||
}
|
||||
SDCommands::Format => {
|
||||
let answer = prompt(format!(
|
||||
"{}",
|
||||
"Do you really want to format the SD card? [y/N] ".bold()
|
||||
));
|
||||
if answer.to_ascii_lowercase() != "y" {
|
||||
println!("{}", "Format operation aborted".red());
|
||||
return Ok(());
|
||||
}
|
||||
log_wait(format!("Formatting the SD card"), || ff.mkfs())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||
let mut sc64 = init_sc64(connection, true)?;
|
||||
|
||||
let (major, minor, revision) = sc64.check_firmware_version()?;
|
||||
let state = sc64.get_device_state()?;
|
||||
let datetime = state.datetime.format("%Y-%m-%d %H:%M:%S");
|
||||
|
||||
println!("{}", "SummerCart64 state information:".bold());
|
||||
println!(" Firmware version: v{}.{}.{}", major, minor, revision);
|
||||
println!(" RTC datetime: {}", datetime);
|
||||
println!(" RTC datetime: {}", state.datetime);
|
||||
println!(" Boot mode: {}", state.boot_mode);
|
||||
println!(" Save type: {}", state.save_type);
|
||||
println!(" CIC seed: {}", state.cic_seed);
|
||||
@ -793,6 +1069,7 @@ fn handle_info_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||
println!(" Button state: {}", state.button_state);
|
||||
println!(" LED blink: {}", state.led_enable);
|
||||
println!(" IS-Viewer 64: {}", state.isviewer);
|
||||
println!(" SD card status: {}", state.sd_card_status);
|
||||
println!("{}", "SummerCart64 diagnostic information:".bold());
|
||||
println!(" PI I/O access: {}", state.fpga_debug_data.pi_io_access);
|
||||
println!(
|
||||
@ -921,19 +1198,30 @@ fn handle_firmware_command(
|
||||
}
|
||||
|
||||
fn handle_test_command(connection: Connection) -> Result<(), sc64::Error> {
|
||||
let mut sc64 = init_sc64(connection, false)?;
|
||||
let mut sc64 = init_sc64(connection, true)?;
|
||||
|
||||
sc64.reset_state()?;
|
||||
|
||||
println!("{}: USB", "[SC64 Tests]".bold());
|
||||
|
||||
print!(" Performing USB read speed test... ");
|
||||
stdout().flush().unwrap();
|
||||
let read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
|
||||
println!("{}", format!("{read_speed:.2} MiB/s",).bright_green());
|
||||
let usb_read_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Read)?;
|
||||
println!("{}", format!("{usb_read_speed:.2} MiB/s",).bright_green());
|
||||
|
||||
print!(" Performing USB write speed test... ");
|
||||
stdout().flush().unwrap();
|
||||
let write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
|
||||
println!("{}", format!("{write_speed:.2} MiB/s",).bright_green());
|
||||
let usb_write_speed = sc64.test_usb_speed(sc64::SpeedTestDirection::Write)?;
|
||||
println!("{}", format!("{usb_write_speed:.2} MiB/s",).bright_green());
|
||||
|
||||
println!("{}: SD card", "[SC64 Tests]".bold());
|
||||
|
||||
print!(" Performing SD card read speed test... ");
|
||||
stdout().flush().unwrap();
|
||||
match sc64.test_sd_card() {
|
||||
Ok(sd_read_speed) => println!("{}", format!("{sd_read_speed:.2} MiB/s",).bright_green()),
|
||||
Err(result) => println!("{}", format!("error! {result}").bright_red()),
|
||||
}
|
||||
|
||||
println!("{}: SDRAM (pattern)", "[SC64 Tests]".bold());
|
||||
|
||||
|
@ -110,35 +110,35 @@ fn calculate_ipl3_checksum(ipl3: &[u8], seed: u8) -> Result<u64, Error> {
|
||||
Ok(checksum)
|
||||
}
|
||||
|
||||
pub fn sign_ipl3(ipl3: &[u8], custom_seed: Option<u8>) -> Result<(u8, u64), Error> {
|
||||
pub fn sign_ipl3(ipl3: &[u8], custom_seed: Option<u8>) -> Result<(u8, u64, bool), Error> {
|
||||
if let Some(seed) = custom_seed {
|
||||
Ok((seed, calculate_ipl3_checksum(ipl3, seed)?))
|
||||
} else {
|
||||
let known_seed_checksum_pairs = [
|
||||
(0xDD, 0x083C6C77E0B1u64), // 5167
|
||||
(0x3F, 0x45CC73EE317Au64), // 6101
|
||||
(0x3F, 0x44160EC5D9AFu64), // 7102
|
||||
(0x3F, 0xA536C0F1D859u64), // 6102/7102
|
||||
(0x78, 0x586FD4709867u64), // 6103/7103
|
||||
(0x91, 0x8618A45BC2D3u64), // 6105/7105
|
||||
(0x85, 0x2BBAD4E6EB74u64), // 6106/7106
|
||||
(0xDD, 0x6EE8D9E84970u64), // NDXJ0
|
||||
(0xDD, 0x6C216495C8B9u64), // NDDJ0
|
||||
(0xDD, 0xE27F43BA93ACu64), // NDDJ1
|
||||
(0xDD, 0x32B294E2AB90u64), // NDDJ2
|
||||
(0xDE, 0x05BA2EF0A5F1u64), // NDDE0
|
||||
];
|
||||
|
||||
for (seed, checksum) in known_seed_checksum_pairs {
|
||||
if calculate_ipl3_checksum(ipl3, seed)? == checksum {
|
||||
return Ok((seed, checksum));
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown IPL3 detected, sign it with arbitrary seed (CIC6102/7101 value is used here)
|
||||
const DEFAULT_SEED: u8 = 0x3F;
|
||||
let checksum = calculate_ipl3_checksum(ipl3, DEFAULT_SEED)?;
|
||||
|
||||
Ok((DEFAULT_SEED, checksum))
|
||||
return Ok((seed, calculate_ipl3_checksum(ipl3, seed)?, true));
|
||||
}
|
||||
|
||||
let known_seed_checksum_pairs = [
|
||||
(0xDD, 0x083C6C77E0B1u64), // 5167
|
||||
(0x3F, 0x45CC73EE317Au64), // 6101
|
||||
(0x3F, 0x44160EC5D9AFu64), // 7102
|
||||
(0x3F, 0xA536C0F1D859u64), // 6102/7102
|
||||
(0x78, 0x586FD4709867u64), // 6103/7103
|
||||
(0x91, 0x8618A45BC2D3u64), // 6105/7105
|
||||
(0x85, 0x2BBAD4E6EB74u64), // 6106/7106
|
||||
(0xDD, 0x6EE8D9E84970u64), // NDXJ0
|
||||
(0xDD, 0x6C216495C8B9u64), // NDDJ0
|
||||
(0xDD, 0xE27F43BA93ACu64), // NDDJ1
|
||||
(0xDD, 0x32B294E2AB90u64), // NDDJ2
|
||||
(0xDE, 0x05BA2EF0A5F1u64), // NDDE0
|
||||
];
|
||||
|
||||
for (seed, checksum) in known_seed_checksum_pairs {
|
||||
if calculate_ipl3_checksum(ipl3, seed)? == checksum {
|
||||
return Ok((seed, checksum, true));
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown IPL3 detected, sign it with arbitrary seed (CIC6102/7101 value is used here)
|
||||
const DEFAULT_SEED: u8 = 0x3F;
|
||||
let checksum = calculate_ipl3_checksum(ipl3, DEFAULT_SEED)?;
|
||||
|
||||
Ok((DEFAULT_SEED, checksum, false))
|
||||
}
|
||||
|
@ -26,3 +26,9 @@ impl From<std::io::Error> for Error {
|
||||
Error::new(format!("IO error: {}", value).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::ff::Error> for Error {
|
||||
fn from(value: super::ff::Error) -> Self {
|
||||
Error::new(format!("FatFs error: {}", value).as_str())
|
||||
}
|
||||
}
|
||||
|
637
sw/deployer/src/sc64/ff.rs
Normal file
637
sw/deployer/src/sc64/ff.rs
Normal file
@ -0,0 +1,637 @@
|
||||
use super::{SdCardResult, SC64, SD_CARD_SECTOR_SIZE};
|
||||
use chrono::{Datelike, Timelike};
|
||||
mod fatfs {
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_upper_case_globals)]
|
||||
#![allow(unused)]
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/fatfs_bindings.rs"));
|
||||
|
||||
pub type DSTATUS = BYTE;
|
||||
pub const DSTATUS_STA_OK: DSTATUS = 0;
|
||||
pub const DSTATUS_STA_NOINIT: DSTATUS = 1 << 0;
|
||||
pub const DSTATUS_STA_NODISK: DSTATUS = 1 << 2;
|
||||
pub const DSTATUS_STA_PROTECT: DSTATUS = 1 << 3;
|
||||
|
||||
pub type DRESULT = std::os::raw::c_uint;
|
||||
pub const DRESULT_RES_OK: DRESULT = 0;
|
||||
pub const DRESULT_RES_ERROR: DRESULT = 1;
|
||||
pub const DRESULT_RES_WRPRT: DRESULT = 2;
|
||||
pub const DRESULT_RES_NOTRDY: DRESULT = 3;
|
||||
pub const DRESULT_RES_PARERR: DRESULT = 4;
|
||||
|
||||
pub const CTRL_SYNC: BYTE = 0;
|
||||
pub const GET_SECTOR_COUNT: BYTE = 1;
|
||||
pub const GET_SECTOR_SIZE: BYTE = 2;
|
||||
pub const GET_BLOCK_SIZE: BYTE = 3;
|
||||
pub const CTRL_TRIM: BYTE = 4;
|
||||
|
||||
pub enum Error {
|
||||
DiskErr,
|
||||
IntErr,
|
||||
NotReady,
|
||||
NoFile,
|
||||
NoPath,
|
||||
InvalidName,
|
||||
Denied,
|
||||
Exist,
|
||||
InvalidObject,
|
||||
WriteProtected,
|
||||
InvalidDrive,
|
||||
NotEnabled,
|
||||
NoFilesystem,
|
||||
MkfsAborted,
|
||||
Timeout,
|
||||
Locked,
|
||||
NotEnoughCore,
|
||||
TooManyOpenFiles,
|
||||
InvalidParameter,
|
||||
DriverInstalled,
|
||||
DriverNotInstalled,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::DiskErr => "A hard error occurred in the low level disk I/O layer",
|
||||
Self::IntErr => "Assertion failed",
|
||||
Self::NotReady => "The physical drive does not work",
|
||||
Self::NoFile => "Could not find the file",
|
||||
Self::NoPath => "Could not find the path",
|
||||
Self::InvalidName => "The path name format is invalid",
|
||||
Self::Denied => "Access denied due to a prohibited access or directory full",
|
||||
Self::Exist => "Access denied due to a prohibited access",
|
||||
Self::InvalidObject => "The file/directory object is invalid",
|
||||
Self::WriteProtected => "The physical drive is write protected",
|
||||
Self::InvalidDrive => "The logical drive number is invalid",
|
||||
Self::NotEnabled => "The volume has no work area",
|
||||
Self::NoFilesystem => "Could not find a valid FAT volume",
|
||||
Self::MkfsAborted => "The f_mkfs function aborted due to some problem",
|
||||
Self::Timeout => "Could not take control of the volume within defined period",
|
||||
Self::Locked => "The operation is rejected according to the file sharing policy",
|
||||
Self::NotEnoughCore => "LFN working buffer could not be allocated or given buffer is insufficient in size",
|
||||
Self::TooManyOpenFiles => "Number of open files > FF_FS_LOCK",
|
||||
Self::InvalidParameter => "Given parameter is invalid",
|
||||
Self::DriverInstalled => "FatFs driver is already installed",
|
||||
Self::DriverNotInstalled => "FatFs driver is not installed",
|
||||
Self::Unknown => "Unknown error",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FRESULT> for Error {
|
||||
fn from(value: FRESULT) -> Self {
|
||||
match value {
|
||||
FRESULT_FR_DISK_ERR => Error::DiskErr,
|
||||
FRESULT_FR_INT_ERR => Error::IntErr,
|
||||
FRESULT_FR_NOT_READY => Error::NotReady,
|
||||
FRESULT_FR_NO_FILE => Error::NoFile,
|
||||
FRESULT_FR_NO_PATH => Error::NoPath,
|
||||
FRESULT_FR_INVALID_NAME => Error::InvalidName,
|
||||
FRESULT_FR_DENIED => Error::Denied,
|
||||
FRESULT_FR_EXIST => Error::Exist,
|
||||
FRESULT_FR_INVALID_OBJECT => Error::InvalidObject,
|
||||
FRESULT_FR_WRITE_PROTECTED => Error::WriteProtected,
|
||||
FRESULT_FR_INVALID_DRIVE => Error::InvalidDrive,
|
||||
FRESULT_FR_NOT_ENABLED => Error::NotEnabled,
|
||||
FRESULT_FR_NO_FILESYSTEM => Error::NoFilesystem,
|
||||
FRESULT_FR_MKFS_ABORTED => Error::MkfsAborted,
|
||||
FRESULT_FR_TIMEOUT => Error::Timeout,
|
||||
FRESULT_FR_LOCKED => Error::Locked,
|
||||
FRESULT_FR_NOT_ENOUGH_CORE => Error::NotEnoughCore,
|
||||
FRESULT_FR_TOO_MANY_OPEN_FILES => Error::TooManyOpenFiles,
|
||||
FRESULT_FR_INVALID_PARAMETER => Error::InvalidParameter,
|
||||
_ => Error::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path<P: AsRef<std::path::Path>>(path: P) -> Result<std::ffi::CString, Error> {
|
||||
match path.as_ref().to_str() {
|
||||
Some(path) => Ok(std::ffi::CString::new(path).map_err(|_| Error::InvalidParameter)?),
|
||||
None => Err(Error::InvalidParameter),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Error = fatfs::Error;
|
||||
|
||||
static mut DRIVER: std::sync::Mutex<Option<Box<dyn FFDriver>>> = std::sync::Mutex::new(None);
|
||||
|
||||
fn install_driver(driver: impl FFDriver + 'static) -> Result<(), Error> {
|
||||
let mut d = unsafe { DRIVER.lock().unwrap() };
|
||||
if d.is_some() {
|
||||
return Err(Error::DriverInstalled);
|
||||
}
|
||||
d.replace(Box::new(driver));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn uninstall_driver() -> Result<(), Error> {
|
||||
let mut d = unsafe { DRIVER.lock().unwrap() };
|
||||
if d.is_none() {
|
||||
return Err(Error::DriverNotInstalled);
|
||||
}
|
||||
d.take().unwrap().deinit();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct FatFs {
|
||||
fs: Box<fatfs::FATFS>,
|
||||
}
|
||||
|
||||
impl FatFs {
|
||||
pub fn new(driver: impl FFDriver + 'static) -> Result<Self, Error> {
|
||||
install_driver(driver)?;
|
||||
let mut ff = Self {
|
||||
fs: Box::new(unsafe { std::mem::zeroed() }),
|
||||
};
|
||||
ff.mount(false)?;
|
||||
Ok(ff)
|
||||
}
|
||||
|
||||
fn mount(&mut self, force: bool) -> Result<(), Error> {
|
||||
let opt = if force { 1 } else { 0 };
|
||||
match unsafe { fatfs::f_mount(&mut *self.fs, fatfs::path("")?.as_ptr(), opt) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(()),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn unmount(&mut self) -> Result<(), Error> {
|
||||
match unsafe { fatfs::f_mount(std::ptr::null_mut(), fatfs::path("")?.as_ptr(), 0) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(()),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<File, Error> {
|
||||
File::open(
|
||||
path,
|
||||
fatfs::FA_OPEN_EXISTING | fatfs::FA_READ | fatfs::FA_WRITE,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<File, Error> {
|
||||
File::open(
|
||||
path,
|
||||
fatfs::FA_CREATE_ALWAYS | fatfs::FA_READ | fatfs::FA_WRITE,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn stat<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Entry, Error> {
|
||||
let mut fno = unsafe { std::mem::zeroed() };
|
||||
match unsafe { fatfs::f_stat(fatfs::path(path)?.as_ptr(), &mut fno) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(fno.into()),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opendir<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Directory, Error> {
|
||||
Directory::open(path)
|
||||
}
|
||||
|
||||
pub fn list<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<Vec<Entry>, Error> {
|
||||
let mut dir = self.opendir(path)?;
|
||||
|
||||
let mut list = vec![];
|
||||
|
||||
while let Some(entry) = dir.read()? {
|
||||
list.push(entry);
|
||||
}
|
||||
|
||||
list.sort_by_key(|k| (k.info, k.name.to_lowercase()));
|
||||
|
||||
Ok(list)
|
||||
}
|
||||
|
||||
pub fn mkdir<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||
match unsafe { fatfs::f_mkdir(fatfs::path(path)?.as_ptr()) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(()),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete<P: AsRef<std::path::Path>>(&mut self, path: P) -> Result<(), Error> {
|
||||
match unsafe { fatfs::f_unlink(fatfs::path(path)?.as_ptr()) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(()),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rename<P: AsRef<std::path::Path>>(&mut self, old: P, new: P) -> Result<(), Error> {
|
||||
match unsafe { fatfs::f_rename(fatfs::path(old)?.as_ptr(), fatfs::path(new)?.as_ptr()) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(()),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mkfs(&mut self) -> Result<(), Error> {
|
||||
let mut work = [0u8; 16 * 1024];
|
||||
match unsafe {
|
||||
fatfs::f_mkfs(
|
||||
fatfs::path("")?.as_ptr(),
|
||||
std::ptr::null(),
|
||||
work.as_mut_ptr().cast(),
|
||||
size_of_val(&work) as u32,
|
||||
)
|
||||
} {
|
||||
fatfs::FRESULT_FR_OK => Ok(()),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for FatFs {
|
||||
fn drop(&mut self) {
|
||||
self.unmount().ok();
|
||||
uninstall_driver().ok();
|
||||
}
|
||||
}
|
||||
|
||||
pub enum IOCtl {
|
||||
Sync,
|
||||
GetSectorCount(fatfs::LBA_t),
|
||||
GetSectorSize(fatfs::WORD),
|
||||
GetBlockSize(fatfs::DWORD),
|
||||
Trim,
|
||||
}
|
||||
|
||||
pub trait FFDriver {
|
||||
fn init(&mut self) -> fatfs::DSTATUS;
|
||||
fn deinit(&mut self);
|
||||
fn status(&mut self) -> fatfs::DSTATUS;
|
||||
fn read(&mut self, buffer: &mut [u8], sector: fatfs::LBA_t) -> fatfs::DRESULT;
|
||||
fn write(&mut self, buffer: &[u8], sector: fatfs::LBA_t) -> fatfs::DRESULT;
|
||||
fn ioctl(&mut self, ioctl: &mut IOCtl) -> fatfs::DRESULT;
|
||||
}
|
||||
|
||||
impl FFDriver for SC64 {
|
||||
fn init(&mut self) -> fatfs::DSTATUS {
|
||||
if let Ok(SdCardResult::OK) = self.init_sd_card() {
|
||||
return fatfs::DSTATUS_STA_OK;
|
||||
}
|
||||
fatfs::DSTATUS_STA_NOINIT
|
||||
}
|
||||
|
||||
fn deinit(&mut self) {
|
||||
self.deinit_sd_card().ok();
|
||||
}
|
||||
|
||||
fn status(&mut self) -> fatfs::DSTATUS {
|
||||
if let Ok(status) = self.get_sd_card_status() {
|
||||
if status.card_initialized {
|
||||
return fatfs::DSTATUS_STA_OK;
|
||||
}
|
||||
}
|
||||
fatfs::DSTATUS_STA_NOINIT
|
||||
}
|
||||
|
||||
fn read(&mut self, buffer: &mut [u8], sector: fatfs::LBA_t) -> fatfs::DRESULT {
|
||||
if (sector + ((buffer.len() / SD_CARD_SECTOR_SIZE) as fatfs::LBA_t)) > 0x1_0000_0000 {
|
||||
return fatfs::DRESULT_RES_PARERR;
|
||||
}
|
||||
if let Ok(SdCardResult::OK) = self.read_sd_card(buffer, sector as u32) {
|
||||
return fatfs::DRESULT_RES_OK;
|
||||
}
|
||||
fatfs::DRESULT_RES_ERROR
|
||||
}
|
||||
|
||||
fn write(&mut self, buffer: &[u8], sector: fatfs::LBA_t) -> fatfs::DRESULT {
|
||||
if (sector + ((buffer.len() / SD_CARD_SECTOR_SIZE) as fatfs::LBA_t)) > 0x1_0000_0000 {
|
||||
return fatfs::DRESULT_RES_PARERR;
|
||||
}
|
||||
if let Ok(SdCardResult::OK) = self.write_sd_card(buffer, sector as u32) {
|
||||
return fatfs::DRESULT_RES_OK;
|
||||
}
|
||||
fatfs::DRESULT_RES_ERROR
|
||||
}
|
||||
|
||||
fn ioctl(&mut self, ioctl: &mut IOCtl) -> fatfs::DRESULT {
|
||||
match ioctl {
|
||||
IOCtl::Sync => {}
|
||||
IOCtl::GetSectorCount(_) => {
|
||||
match self.get_sd_card_info() {
|
||||
Ok(info) => *ioctl = IOCtl::GetSectorCount(info.sectors as fatfs::LBA_t),
|
||||
Err(_) => return fatfs::DRESULT_RES_ERROR,
|
||||
};
|
||||
}
|
||||
IOCtl::GetSectorSize(_) => {
|
||||
*ioctl = IOCtl::GetSectorSize(SD_CARD_SECTOR_SIZE as fatfs::WORD)
|
||||
}
|
||||
IOCtl::GetBlockSize(_) => {
|
||||
*ioctl = IOCtl::GetBlockSize(1);
|
||||
}
|
||||
IOCtl::Trim => {}
|
||||
}
|
||||
fatfs::DRESULT_RES_OK
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn disk_status(pdrv: fatfs::BYTE) -> fatfs::DSTATUS {
|
||||
if pdrv != 0 {
|
||||
return fatfs::DSTATUS_STA_NOINIT;
|
||||
}
|
||||
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||
return d.status();
|
||||
}
|
||||
fatfs::DSTATUS_STA_NOINIT
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn disk_initialize(pdrv: fatfs::BYTE) -> fatfs::DSTATUS {
|
||||
if pdrv != 0 {
|
||||
return fatfs::DSTATUS_STA_NOINIT;
|
||||
}
|
||||
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||
return d.init();
|
||||
}
|
||||
fatfs::DSTATUS_STA_NOINIT
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn disk_read(
|
||||
pdrv: fatfs::BYTE,
|
||||
buff: *mut fatfs::BYTE,
|
||||
sector: fatfs::LBA_t,
|
||||
count: fatfs::UINT,
|
||||
) -> fatfs::DRESULT {
|
||||
if pdrv != 0 {
|
||||
return fatfs::DRESULT_RES_PARERR;
|
||||
}
|
||||
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||
return d.read(
|
||||
&mut *std::ptr::slice_from_raw_parts_mut(buff, (count as usize) * SD_CARD_SECTOR_SIZE),
|
||||
sector,
|
||||
);
|
||||
}
|
||||
fatfs::DRESULT_RES_NOTRDY
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn disk_write(
|
||||
pdrv: fatfs::BYTE,
|
||||
buff: *mut fatfs::BYTE,
|
||||
sector: fatfs::LBA_t,
|
||||
count: fatfs::UINT,
|
||||
) -> fatfs::DRESULT {
|
||||
if pdrv != 0 {
|
||||
return fatfs::DRESULT_RES_PARERR;
|
||||
}
|
||||
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||
return d.write(
|
||||
&*std::ptr::slice_from_raw_parts(buff, (count as usize) * SD_CARD_SECTOR_SIZE),
|
||||
sector,
|
||||
);
|
||||
}
|
||||
fatfs::DRESULT_RES_NOTRDY
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn disk_ioctl(
|
||||
pdrv: fatfs::BYTE,
|
||||
cmd: fatfs::BYTE,
|
||||
buff: *mut std::os::raw::c_void,
|
||||
) -> fatfs::DRESULT {
|
||||
if pdrv != 0 {
|
||||
return fatfs::DRESULT_RES_PARERR;
|
||||
}
|
||||
let mut ioctl = match cmd {
|
||||
fatfs::CTRL_SYNC => IOCtl::Sync,
|
||||
fatfs::GET_SECTOR_COUNT => IOCtl::GetSectorCount(0),
|
||||
fatfs::GET_SECTOR_SIZE => IOCtl::GetSectorSize(0),
|
||||
fatfs::GET_BLOCK_SIZE => IOCtl::GetBlockSize(0),
|
||||
fatfs::CTRL_TRIM => IOCtl::Trim,
|
||||
_ => return fatfs::DRESULT_RES_PARERR,
|
||||
};
|
||||
if let Some(d) = DRIVER.lock().unwrap().as_mut() {
|
||||
let result = d.ioctl(&mut ioctl);
|
||||
if result == fatfs::DRESULT_RES_OK {
|
||||
match ioctl {
|
||||
IOCtl::GetSectorCount(count) => {
|
||||
buff.copy_from(std::ptr::addr_of!(count).cast(), size_of_val(&count))
|
||||
}
|
||||
IOCtl::GetSectorSize(size) => {
|
||||
buff.copy_from(std::ptr::addr_of!(size).cast(), size_of_val(&size))
|
||||
}
|
||||
IOCtl::GetBlockSize(size) => {
|
||||
buff.copy_from(std::ptr::addr_of!(size).cast(), size_of_val(&size))
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
fatfs::DRESULT_RES_NOTRDY
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn get_fattime() -> fatfs::DWORD {
|
||||
let now = chrono::Local::now();
|
||||
let year = now.year() as u32;
|
||||
let month = now.month();
|
||||
let day = now.day();
|
||||
let hour = now.hour();
|
||||
let minute = now.minute();
|
||||
let second = now.second();
|
||||
((year - 1980) << 25)
|
||||
| (month << 21)
|
||||
| (day << 16)
|
||||
| (hour << 11)
|
||||
| (minute << 5)
|
||||
| (second << 1)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Entry {
|
||||
pub info: EntryInfo,
|
||||
pub name: String,
|
||||
pub datetime: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Ord)]
|
||||
pub enum EntryInfo {
|
||||
Directory,
|
||||
File { size: u64 },
|
||||
}
|
||||
|
||||
impl PartialOrd for EntryInfo {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(match (self, other) {
|
||||
(EntryInfo::Directory, EntryInfo::File { size: _ }) => std::cmp::Ordering::Less,
|
||||
(EntryInfo::File { size: _ }, EntryInfo::Directory) => std::cmp::Ordering::Greater,
|
||||
_ => std::cmp::Ordering::Equal,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for EntryInfo {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
Ok(match self {
|
||||
EntryInfo::Directory => f.write_fmt(format_args!("d ----",))?,
|
||||
EntryInfo::File { size } => {
|
||||
const UNITS: [&str; 4] = ["K", "M", "G", "T"];
|
||||
let mut reduced_size = *size as f64;
|
||||
let mut reduced_unit = "B";
|
||||
for unit in UNITS {
|
||||
if reduced_size >= 1000.0 {
|
||||
reduced_size /= 1024.0;
|
||||
reduced_unit = unit;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let formatted_size = if *size >= 1000 && reduced_size <= 9.9 {
|
||||
format!("{:>1.1}{}", reduced_size, reduced_unit)
|
||||
} else {
|
||||
format!("{:>3.0}{}", reduced_size, reduced_unit)
|
||||
};
|
||||
f.write_fmt(format_args!("f {formatted_size}"))?
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fatfs::FILINFO> for Entry {
|
||||
fn from(value: fatfs::FILINFO) -> Self {
|
||||
let name = unsafe { std::ffi::CStr::from_ptr(value.fname.as_ptr()) }
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let datetime = chrono::NaiveDateTime::new(
|
||||
chrono::NaiveDate::from_ymd_opt(
|
||||
(1980 + ((value.fdate >> 9) & 0x7F)).into(),
|
||||
((value.fdate >> 5) & 0xF).into(),
|
||||
(value.fdate & 0x1F).into(),
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
chrono::NaiveTime::from_hms_opt(
|
||||
((value.ftime >> 11) & 0x1F).into(),
|
||||
((value.ftime >> 5) & 0x3F).into(),
|
||||
((value.ftime & 0x1F) * 2).into(),
|
||||
)
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
let info = if (value.fattrib as u32 & fatfs::AM_DIR) == 0 {
|
||||
EntryInfo::File { size: value.fsize }
|
||||
} else {
|
||||
EntryInfo::Directory
|
||||
};
|
||||
Self {
|
||||
name,
|
||||
datetime,
|
||||
info,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Directory {
|
||||
dir: fatfs::DIR,
|
||||
}
|
||||
|
||||
impl Directory {
|
||||
fn open<P: AsRef<std::path::Path>>(path: P) -> Result<Self, Error> {
|
||||
let mut dir = unsafe { std::mem::zeroed() };
|
||||
match unsafe { fatfs::f_opendir(&mut dir, fatfs::path(path)?.as_ptr()) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(Self { dir }),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self) -> Result<Option<Entry>, Error> {
|
||||
let mut fno = unsafe { std::mem::zeroed() };
|
||||
match unsafe { fatfs::f_readdir(&mut self.dir, &mut fno) } {
|
||||
fatfs::FRESULT_FR_OK => {
|
||||
if fno.fname[0] == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(fno.into()))
|
||||
}
|
||||
}
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Directory {
|
||||
fn drop(&mut self) {
|
||||
unsafe { fatfs::f_closedir(&mut self.dir) };
|
||||
}
|
||||
}
|
||||
|
||||
pub struct File {
|
||||
fil: fatfs::FIL,
|
||||
}
|
||||
|
||||
impl File {
|
||||
fn open<P: AsRef<std::path::Path>>(path: P, mode: u32) -> Result<File, Error> {
|
||||
let mut fil = unsafe { std::mem::zeroed() };
|
||||
match unsafe { fatfs::f_open(&mut fil, fatfs::path(path)?.as_ptr(), mode as u8) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(File { fil }),
|
||||
error => Err(error.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Read for File {
|
||||
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||
let mut bytes_read = 0;
|
||||
match unsafe {
|
||||
fatfs::f_read(
|
||||
&mut self.fil,
|
||||
buf.as_mut_ptr().cast(),
|
||||
buf.len() as fatfs::UINT,
|
||||
&mut bytes_read,
|
||||
)
|
||||
} {
|
||||
fatfs::FRESULT_FR_OK => Ok(bytes_read as usize),
|
||||
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Write for File {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
let mut bytes_written = 0;
|
||||
match unsafe {
|
||||
fatfs::f_write(
|
||||
&mut self.fil,
|
||||
buf.as_ptr().cast(),
|
||||
buf.len() as fatfs::UINT,
|
||||
&mut bytes_written,
|
||||
)
|
||||
} {
|
||||
fatfs::FRESULT_FR_OK => Ok(bytes_written as usize),
|
||||
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
match unsafe { fatfs::f_sync(&mut self.fil) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(()),
|
||||
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::io::Seek for File {
|
||||
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
|
||||
let ofs = match pos {
|
||||
std::io::SeekFrom::Current(offset) => self.fil.fptr.saturating_add_signed(offset),
|
||||
std::io::SeekFrom::Start(offset) => offset,
|
||||
std::io::SeekFrom::End(offset) => self.fil.obj.objsize.saturating_add_signed(offset),
|
||||
};
|
||||
match unsafe { fatfs::f_lseek(&mut self.fil, ofs) } {
|
||||
fatfs::FRESULT_FR_OK => Ok(self.fil.fptr),
|
||||
_ => Err(std::io::ErrorKind::BrokenPipe.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for File {
|
||||
fn drop(&mut self) {
|
||||
unsafe { fatfs::f_close(&mut self.fil) };
|
||||
}
|
||||
}
|
@ -73,8 +73,8 @@ impl Wrapper {
|
||||
let result = if devices > 0 {
|
||||
let mut list: Vec<DeviceInfo> = vec![];
|
||||
|
||||
let mut description = [0i8; 128];
|
||||
let mut serial = [0i8; 128];
|
||||
let mut description = [std::ffi::c_char::from(0); 128];
|
||||
let mut serial = [std::ffi::c_char::from(0); 128];
|
||||
|
||||
let mut device = device_list;
|
||||
let mut index = 0;
|
||||
|
@ -1,5 +1,6 @@
|
||||
mod cic;
|
||||
mod error;
|
||||
pub mod ff;
|
||||
pub mod firmware;
|
||||
mod ftdi;
|
||||
mod link;
|
||||
@ -13,10 +14,11 @@ pub use self::{
|
||||
link::list_local_devices,
|
||||
server::ServerEvent,
|
||||
types::{
|
||||
AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState,
|
||||
AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, CicStep, DataPacket, DdDiskState,
|
||||
DdDriveType, DdMode, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind,
|
||||
FpgaDebugData, ISViewer, MemoryTestPattern, MemoryTestPatternResult, SaveType,
|
||||
SaveWriteback, SpeedTestDirection, Switch, TvType,
|
||||
SaveWriteback, SdCardInfo, SdCardOpPacket, SdCardResult, SdCardStatus, SpeedTestDirection,
|
||||
Switch, TvType,
|
||||
},
|
||||
};
|
||||
|
||||
@ -25,7 +27,8 @@ use self::{
|
||||
link::Link,
|
||||
time::{convert_from_datetime, convert_to_datetime},
|
||||
types::{
|
||||
get_config, get_setting, Config, ConfigId, FirmwareStatus, Setting, SettingId, UpdateStatus,
|
||||
get_config, get_setting, Config, ConfigId, FirmwareStatus, SdCardOp, Setting, SettingId,
|
||||
UpdateStatus,
|
||||
},
|
||||
};
|
||||
use chrono::NaiveDateTime;
|
||||
@ -58,6 +61,7 @@ pub struct DeviceState {
|
||||
pub button_mode: ButtonMode,
|
||||
pub rom_extended_enable: Switch,
|
||||
pub led_enable: Switch,
|
||||
pub sd_card_status: SdCardStatus,
|
||||
pub datetime: NaiveDateTime,
|
||||
pub fpga_debug_data: FpgaDebugData,
|
||||
pub diagnostic_data: DiagnosticData,
|
||||
@ -66,7 +70,7 @@ pub struct DeviceState {
|
||||
const SC64_V2_IDENTIFIER: &[u8; 4] = b"SCv2";
|
||||
|
||||
const SUPPORTED_MAJOR_VERSION: u16 = 2;
|
||||
const SUPPORTED_MINOR_VERSION: u16 = 19;
|
||||
const SUPPORTED_MINOR_VERSION: u16 = 20;
|
||||
|
||||
const SDRAM_ADDRESS: u32 = 0x0000_0000;
|
||||
const SDRAM_LENGTH: usize = 64 * 1024 * 1024;
|
||||
@ -94,13 +98,18 @@ const SRAM_1M_LENGTH: usize = 128 * 1024;
|
||||
|
||||
const BOOTLOADER_ADDRESS: u32 = 0x04E0_0000;
|
||||
|
||||
const SD_CARD_BUFFER_ADDRESS: u32 = 0x03FE_0000; // Arbitrary offset in SDRAM memory
|
||||
const SD_CARD_BUFFER_LENGTH: usize = 128 * 1024; // Arbitrary length in SDRAM memory
|
||||
|
||||
pub const SD_CARD_SECTOR_SIZE: usize = 512;
|
||||
|
||||
const FIRMWARE_ADDRESS_SDRAM: u32 = 0x0010_0000; // Arbitrary offset in SDRAM memory
|
||||
const FIRMWARE_ADDRESS_FLASH: u32 = 0x0410_0000; // Arbitrary offset in Flash memory
|
||||
const FIRMWARE_UPDATE_TIMEOUT: Duration = Duration::from_secs(90);
|
||||
|
||||
const ISV_BUFFER_LENGTH: usize = 64 * 1024;
|
||||
|
||||
pub const MEMORY_LENGTH: usize = 0x0500_2980;
|
||||
pub const MEMORY_LENGTH: usize = 0x0500_2C80;
|
||||
|
||||
const MEMORY_CHUNK_LENGTH: usize = 1 * 1024 * 1024;
|
||||
|
||||
@ -235,6 +244,53 @@ impl SC64 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn command_sd_card_operation(&mut self, op: SdCardOp) -> Result<SdCardOpPacket, Error> {
|
||||
let data = self
|
||||
.link
|
||||
.execute_command_raw(b'i', op.into(), &[], false, true)?;
|
||||
if data.len() != 8 {
|
||||
return Err(Error::new(
|
||||
"Invalid data length received for SD card operation command",
|
||||
));
|
||||
}
|
||||
Ok(SdCardOpPacket {
|
||||
result: data.clone().try_into()?,
|
||||
status: data.clone().try_into()?,
|
||||
})
|
||||
}
|
||||
|
||||
fn command_sd_card_read(
|
||||
&mut self,
|
||||
address: u32,
|
||||
sector: u32,
|
||||
count: u32,
|
||||
) -> Result<SdCardResult, Error> {
|
||||
let data = self.link.execute_command_raw(
|
||||
b's',
|
||||
[address, count],
|
||||
§or.to_be_bytes(),
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
Ok(data.try_into()?)
|
||||
}
|
||||
|
||||
fn command_sd_card_write(
|
||||
&mut self,
|
||||
address: u32,
|
||||
sector: u32,
|
||||
count: u32,
|
||||
) -> Result<SdCardResult, Error> {
|
||||
let data = self.link.execute_command_raw(
|
||||
b'S',
|
||||
[address, count],
|
||||
§or.to_be_bytes(),
|
||||
false,
|
||||
true,
|
||||
)?;
|
||||
Ok(data.try_into()?)
|
||||
}
|
||||
|
||||
fn command_dd_set_block_ready(&mut self, error: bool) -> Result<(), Error> {
|
||||
self.link.execute_command(b'D', [error as u32, 0], &[])?;
|
||||
Ok(())
|
||||
@ -389,6 +445,7 @@ impl SC64 {
|
||||
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
|
||||
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
|
||||
SaveType::FlashramFake => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||
};
|
||||
|
||||
if length != save_length {
|
||||
@ -413,6 +470,7 @@ impl SC64 {
|
||||
SaveType::Flashram => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||
SaveType::SramBanked => (SAVE_ADDRESS, SRAM_BANKED_LENGTH),
|
||||
SaveType::Sram1m => (SAVE_ADDRESS, SRAM_1M_LENGTH),
|
||||
SaveType::FlashramFake => (SAVE_ADDRESS, FLASHRAM_LENGTH),
|
||||
};
|
||||
|
||||
self.memory_read_chunked(writer, address, save_length)
|
||||
@ -430,7 +488,10 @@ impl SC64 {
|
||||
self.memory_read_chunked(writer, address, length)
|
||||
}
|
||||
|
||||
pub fn calculate_cic_parameters(&mut self, custom_seed: Option<u8>) -> Result<(), Error> {
|
||||
pub fn calculate_cic_parameters(
|
||||
&mut self,
|
||||
custom_seed: Option<u8>,
|
||||
) -> Result<(u8, u64, bool), Error> {
|
||||
let (address, cic_seed, boot_seed) = match get_config!(self, BootMode)? {
|
||||
BootMode::Menu => (BOOTLOADER_ADDRESS, None, None),
|
||||
BootMode::Rom => (BOOTLOADER_ADDRESS, None, custom_seed),
|
||||
@ -439,12 +500,12 @@ impl SC64 {
|
||||
BootMode::DirectDdIpl => (DDIPL_ADDRESS, custom_seed, None),
|
||||
};
|
||||
let ipl3 = self.command_memory_read(address + IPL3_OFFSET, IPL3_LENGTH)?;
|
||||
let (seed, checksum) = sign_ipl3(&ipl3, cic_seed)?;
|
||||
let (seed, checksum, matched) = sign_ipl3(&ipl3, cic_seed)?;
|
||||
self.command_cic_params_set(false, seed, checksum)?;
|
||||
if let Some(seed) = boot_seed {
|
||||
self.command_config_set(Config::CicSeed(CicSeed::Seed(seed)))?;
|
||||
}
|
||||
Ok(())
|
||||
Ok((seed, checksum, matched))
|
||||
}
|
||||
|
||||
pub fn set_boot_mode(&mut self, boot_mode: BootMode) -> Result<(), Error> {
|
||||
@ -489,6 +550,7 @@ impl SC64 {
|
||||
button_mode: get_config!(self, ButtonMode)?,
|
||||
rom_extended_enable: get_config!(self, RomExtendedEnable)?,
|
||||
led_enable: get_setting!(self, LedEnable)?,
|
||||
sd_card_status: self.get_sd_card_status()?,
|
||||
datetime: self.get_datetime()?,
|
||||
fpga_debug_data: self.command_fpga_debug_data_get()?,
|
||||
diagnostic_data: self.command_diagnostic_data_get()?,
|
||||
@ -606,6 +668,83 @@ impl SC64 {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn init_sd_card(&mut self) -> Result<SdCardResult, Error> {
|
||||
self.command_sd_card_operation(SdCardOp::Init)
|
||||
.map(|packet| packet.result)
|
||||
}
|
||||
|
||||
pub fn deinit_sd_card(&mut self) -> Result<SdCardResult, Error> {
|
||||
self.command_sd_card_operation(SdCardOp::Deinit)
|
||||
.map(|packet| packet.result)
|
||||
}
|
||||
|
||||
pub fn get_sd_card_status(&mut self) -> Result<SdCardStatus, Error> {
|
||||
self.command_sd_card_operation(SdCardOp::GetStatus)
|
||||
.map(|reply| reply.status)
|
||||
}
|
||||
|
||||
pub fn get_sd_card_info(&mut self) -> Result<SdCardInfo, Error> {
|
||||
const SD_CARD_INFO_BUFFER_ADDRESS: u32 = 0x0500_2BE0;
|
||||
let info =
|
||||
match self.command_sd_card_operation(SdCardOp::GetInfo(SD_CARD_INFO_BUFFER_ADDRESS))? {
|
||||
SdCardOpPacket {
|
||||
result: SdCardResult::OK,
|
||||
status: _,
|
||||
} => self.command_memory_read(SD_CARD_INFO_BUFFER_ADDRESS, 32)?,
|
||||
packet => {
|
||||
return Err(Error::new(
|
||||
format!("Couldn't get SD card info registers: {}", packet.result).as_str(),
|
||||
))
|
||||
}
|
||||
};
|
||||
Ok(info.try_into()?)
|
||||
}
|
||||
|
||||
pub fn read_sd_card(&mut self, data: &mut [u8], sector: u32) -> Result<SdCardResult, Error> {
|
||||
if data.len() % SD_CARD_SECTOR_SIZE != 0 {
|
||||
return Err(Error::new(
|
||||
"SD card read length not aligned to the sector size",
|
||||
));
|
||||
}
|
||||
|
||||
let mut current_sector = sector;
|
||||
|
||||
for mut chunk in data.chunks_mut(SD_CARD_BUFFER_LENGTH) {
|
||||
let sectors = (chunk.len() / SD_CARD_SECTOR_SIZE) as u32;
|
||||
match self.command_sd_card_read(SD_CARD_BUFFER_ADDRESS, current_sector, sectors)? {
|
||||
SdCardResult::OK => {}
|
||||
result => return Ok(result),
|
||||
}
|
||||
let data = self.command_memory_read(SD_CARD_BUFFER_ADDRESS, chunk.len())?;
|
||||
chunk.write_all(&data)?;
|
||||
current_sector += sectors;
|
||||
}
|
||||
|
||||
Ok(SdCardResult::OK)
|
||||
}
|
||||
|
||||
pub fn write_sd_card(&mut self, data: &[u8], sector: u32) -> Result<SdCardResult, Error> {
|
||||
if data.len() % SD_CARD_SECTOR_SIZE != 0 {
|
||||
return Err(Error::new(
|
||||
"SD card write length not aligned to the sector size",
|
||||
));
|
||||
}
|
||||
|
||||
let mut current_sector = sector;
|
||||
|
||||
for chunk in data.chunks(SD_CARD_BUFFER_LENGTH) {
|
||||
let sectors = (chunk.len() / SD_CARD_SECTOR_SIZE) as u32;
|
||||
self.command_memory_write(SD_CARD_BUFFER_ADDRESS, chunk)?;
|
||||
match self.command_sd_card_write(SD_CARD_BUFFER_ADDRESS, current_sector, sectors)? {
|
||||
SdCardResult::OK => {}
|
||||
result => return Ok(result),
|
||||
}
|
||||
current_sector += sectors;
|
||||
}
|
||||
|
||||
Ok(SdCardResult::OK)
|
||||
}
|
||||
|
||||
pub fn check_device(&mut self) -> Result<(), Error> {
|
||||
let identifier = self.command_identifier_get().map_err(|e| {
|
||||
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())
|
||||
@ -634,6 +773,14 @@ impl SC64 {
|
||||
self.command_state_reset()
|
||||
}
|
||||
|
||||
pub fn is_console_powered_on(&mut self) -> Result<bool, Error> {
|
||||
let debug_data = self.command_fpga_debug_data_get()?;
|
||||
Ok(match debug_data.cic_step {
|
||||
CicStep::Unavailable | CicStep::PowerOff => false,
|
||||
_ => true,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn backup_firmware(&mut self) -> Result<Vec<u8>, Error> {
|
||||
self.command_state_reset()?;
|
||||
let (status, length) = self.command_firmware_backup(FIRMWARE_ADDRESS_SDRAM)?;
|
||||
@ -738,6 +885,46 @@ impl SC64 {
|
||||
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
||||
}
|
||||
|
||||
pub fn test_sd_card(&mut self) -> Result<f64, Error> {
|
||||
const TEST_LENGTH: usize = 4 * 1024 * 1024;
|
||||
const MIB_DIVIDER: f64 = 1024.0 * 1024.0;
|
||||
|
||||
let mut data = vec![0x00; TEST_LENGTH];
|
||||
|
||||
match self.init_sd_card()? {
|
||||
SdCardResult::OK => {}
|
||||
result => {
|
||||
return Err(Error::new(
|
||||
format!("Init SD card failed: {result}").as_str(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let time = std::time::Instant::now();
|
||||
|
||||
match self.read_sd_card(&mut data, 0)? {
|
||||
SdCardResult::OK => {}
|
||||
result => {
|
||||
return Err(Error::new(
|
||||
format!("Read SD card failed: {result}").as_str(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
let elapsed = time.elapsed();
|
||||
|
||||
match self.deinit_sd_card()? {
|
||||
SdCardResult::OK => {}
|
||||
result => {
|
||||
return Err(Error::new(
|
||||
format!("Deinit SD card failed: {result}").as_str(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok((TEST_LENGTH as f64 / MIB_DIVIDER) / elapsed.as_secs_f64())
|
||||
}
|
||||
|
||||
pub fn test_sdram_pattern(
|
||||
&mut self,
|
||||
pattern: MemoryTestPattern,
|
||||
|
@ -288,6 +288,7 @@ pub enum SaveType {
|
||||
Flashram,
|
||||
SramBanked,
|
||||
Sram1m,
|
||||
FlashramFake,
|
||||
}
|
||||
|
||||
impl Display for SaveType {
|
||||
@ -300,6 +301,7 @@ impl Display for SaveType {
|
||||
Self::SramBanked => "SRAM 768k",
|
||||
Self::Flashram => "FlashRAM 1M",
|
||||
Self::Sram1m => "SRAM 1M",
|
||||
Self::FlashramFake => "FakeFlashRAM 1M"
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -315,6 +317,7 @@ impl TryFrom<u32> for SaveType {
|
||||
4 => Self::Flashram,
|
||||
5 => Self::SramBanked,
|
||||
6 => Self::Sram1m,
|
||||
7 => Self::FlashramFake,
|
||||
_ => return Err(Error::new("Unknown save type code")),
|
||||
})
|
||||
}
|
||||
@ -330,6 +333,7 @@ impl From<SaveType> for u32 {
|
||||
SaveType::Flashram => 4,
|
||||
SaveType::SramBanked => 5,
|
||||
SaveType::Sram1m => 6,
|
||||
SaveType::FlashramFake => 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -737,6 +741,241 @@ impl TryFrom<Vec<u8>> for DiskPacket {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SdCardOp {
|
||||
Deinit,
|
||||
Init,
|
||||
GetStatus,
|
||||
GetInfo(u32),
|
||||
ByteSwapOn,
|
||||
ByteSwapOff,
|
||||
}
|
||||
|
||||
impl From<SdCardOp> for [u32; 2] {
|
||||
fn from(value: SdCardOp) -> Self {
|
||||
match value {
|
||||
SdCardOp::Deinit => [0, 0],
|
||||
SdCardOp::Init => [0, 1],
|
||||
SdCardOp::GetStatus => [0, 2],
|
||||
SdCardOp::GetInfo(address) => [address, 3],
|
||||
SdCardOp::ByteSwapOn => [0, 4],
|
||||
SdCardOp::ByteSwapOff => [0, 5],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SdCardResult {
|
||||
OK,
|
||||
NoCardInSlot,
|
||||
NotInitialized,
|
||||
InvalidArgument,
|
||||
InvalidAddress,
|
||||
InvalidOperation,
|
||||
Cmd2IO,
|
||||
Cmd3IO,
|
||||
Cmd6CheckIO,
|
||||
Cmd6CheckCRC,
|
||||
Cmd6CheckTimeout,
|
||||
Cmd6CheckResponse,
|
||||
Cmd6SwitchIO,
|
||||
Cmd6SwitchCRC,
|
||||
Cmd6SwitchTimeout,
|
||||
Cmd6SwitchResponse,
|
||||
Cmd7IO,
|
||||
Cmd8IO,
|
||||
Cmd9IO,
|
||||
Cmd10IO,
|
||||
Cmd18IO,
|
||||
Cmd18CRC,
|
||||
Cmd18Timeout,
|
||||
Cmd25IO,
|
||||
Cmd25CRC,
|
||||
Cmd25Timeout,
|
||||
Acmd6IO,
|
||||
Acmd41IO,
|
||||
Acmd41OCR,
|
||||
Acmd41Timeout,
|
||||
Locked,
|
||||
}
|
||||
|
||||
impl Display for SdCardResult {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::OK => "OK",
|
||||
Self::NoCardInSlot => "No card in slot",
|
||||
Self::NotInitialized => "Not initialized",
|
||||
Self::InvalidArgument => "Invalid argument",
|
||||
Self::InvalidAddress => "Invalid address",
|
||||
Self::InvalidOperation => "Invalid operation",
|
||||
Self::Cmd2IO => "CMD2 I/O",
|
||||
Self::Cmd3IO => "CMD3 I/O",
|
||||
Self::Cmd6CheckIO => "CMD6 I/O (check)",
|
||||
Self::Cmd6CheckCRC => "CMD6 CRC (check)",
|
||||
Self::Cmd6CheckTimeout => "CMD6 timeout (check)",
|
||||
Self::Cmd6CheckResponse => "CMD6 invalid response (check)",
|
||||
Self::Cmd6SwitchIO => "CMD6 I/O (switch)",
|
||||
Self::Cmd6SwitchCRC => "CMD6 CRC (switch)",
|
||||
Self::Cmd6SwitchTimeout => "CMD6 timeout (switch)",
|
||||
Self::Cmd6SwitchResponse => "CMD6 invalid response (switch)",
|
||||
Self::Cmd7IO => "CMD7 I/O",
|
||||
Self::Cmd8IO => "CMD8 I/O",
|
||||
Self::Cmd9IO => "CMD9 I/O",
|
||||
Self::Cmd10IO => "CMD10 I/O",
|
||||
Self::Cmd18IO => "CMD18 I/O",
|
||||
Self::Cmd18CRC => "CMD18 CRC",
|
||||
Self::Cmd18Timeout => "CMD18 timeout",
|
||||
Self::Cmd25IO => "CMD25 I/O",
|
||||
Self::Cmd25CRC => "CMD25 CRC",
|
||||
Self::Cmd25Timeout => "CMD25 timeout",
|
||||
Self::Acmd6IO => "ACMD6 I/O",
|
||||
Self::Acmd41IO => "ACMD41 I/O",
|
||||
Self::Acmd41OCR => "ACMD41 OCR",
|
||||
Self::Acmd41Timeout => "ACMD41 timeout",
|
||||
Self::Locked => "SD card is locked by the N64 side",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for SdCardResult {
|
||||
type Error = Error;
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if value.len() < 4 {
|
||||
return Err(Error::new(
|
||||
"Incorrect data length for SD card result data packet",
|
||||
));
|
||||
}
|
||||
Ok(match u32::from_be_bytes(value[0..4].try_into().unwrap()) {
|
||||
0 => Self::OK,
|
||||
1 => Self::NoCardInSlot,
|
||||
2 => Self::NotInitialized,
|
||||
3 => Self::InvalidArgument,
|
||||
4 => Self::InvalidAddress,
|
||||
5 => Self::InvalidOperation,
|
||||
6 => Self::Cmd2IO,
|
||||
7 => Self::Cmd3IO,
|
||||
8 => Self::Cmd6CheckIO,
|
||||
9 => Self::Cmd6CheckCRC,
|
||||
10 => Self::Cmd6CheckTimeout,
|
||||
11 => Self::Cmd6CheckResponse,
|
||||
12 => Self::Cmd6SwitchIO,
|
||||
13 => Self::Cmd6SwitchCRC,
|
||||
14 => Self::Cmd6SwitchTimeout,
|
||||
15 => Self::Cmd6SwitchResponse,
|
||||
16 => Self::Cmd7IO,
|
||||
17 => Self::Cmd8IO,
|
||||
18 => Self::Cmd9IO,
|
||||
19 => Self::Cmd10IO,
|
||||
20 => Self::Cmd18IO,
|
||||
21 => Self::Cmd18CRC,
|
||||
22 => Self::Cmd18Timeout,
|
||||
23 => Self::Cmd25IO,
|
||||
24 => Self::Cmd25CRC,
|
||||
25 => Self::Cmd25Timeout,
|
||||
26 => Self::Acmd6IO,
|
||||
27 => Self::Acmd41IO,
|
||||
28 => Self::Acmd41OCR,
|
||||
29 => Self::Acmd41Timeout,
|
||||
30 => Self::Locked,
|
||||
_ => return Err(Error::new("Unknown SD card result code")),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SdCardStatus {
|
||||
pub byte_swap: bool,
|
||||
pub clock_mode_50mhz: bool,
|
||||
pub card_type_block: bool,
|
||||
pub card_initialized: bool,
|
||||
pub card_inserted: bool,
|
||||
}
|
||||
|
||||
impl Display for SdCardStatus {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.card_initialized {
|
||||
f.write_str("Initialized, ")?;
|
||||
f.write_str(if self.clock_mode_50mhz {
|
||||
"50 MHz"
|
||||
} else {
|
||||
"25 MHz"
|
||||
})?;
|
||||
if !self.card_type_block {
|
||||
f.write_str(", byte addressed")?;
|
||||
}
|
||||
if self.byte_swap {
|
||||
f.write_str(", byte swap enabled")?;
|
||||
}
|
||||
} else if self.card_inserted {
|
||||
f.write_str("Not initialized")?;
|
||||
} else {
|
||||
f.write_str("Not inserted")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SdCardOpPacket {
|
||||
pub result: SdCardResult,
|
||||
pub status: SdCardStatus,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for SdCardStatus {
|
||||
type Error = Error;
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if value.len() != 8 {
|
||||
return Err(Error::new(
|
||||
"Incorrect data length for SD card status data packet",
|
||||
));
|
||||
}
|
||||
let status = u32::from_be_bytes(value[4..8].try_into().unwrap());
|
||||
return Ok(Self {
|
||||
byte_swap: status & (1 << 4) != 0,
|
||||
clock_mode_50mhz: status & (1 << 3) != 0,
|
||||
card_type_block: status & (1 << 2) != 0,
|
||||
card_initialized: status & (1 << 1) != 0,
|
||||
card_inserted: status & (1 << 0) != 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SdCardInfo {
|
||||
pub sectors: u64,
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for SdCardInfo {
|
||||
type Error = Error;
|
||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if value.len() != 32 {
|
||||
return Err(Error::new(
|
||||
"Incorrect data length for SD card info data packet",
|
||||
));
|
||||
}
|
||||
let csd = u128::from_be_bytes(value[0..16].try_into().unwrap());
|
||||
let csd_structure = (csd >> 126) & 0x3;
|
||||
match csd_structure {
|
||||
0 => {
|
||||
let c_size = ((csd >> 62) & 0xFFF) as u64;
|
||||
let c_size_mult = ((csd >> 47) & 0x7) as u32;
|
||||
let read_bl_len = ((csd >> 80) & 0xF) as u32;
|
||||
Ok(SdCardInfo {
|
||||
sectors: (c_size + 1) * 2u64.pow(c_size_mult + 2) * 2u64.pow(read_bl_len) / 512,
|
||||
})
|
||||
}
|
||||
1 => {
|
||||
let c_size = ((csd >> 48) & 0x3FFFFF) as u64;
|
||||
Ok(SdCardInfo {
|
||||
sectors: (c_size + 1) * 1024,
|
||||
})
|
||||
}
|
||||
2 => {
|
||||
let c_size = ((csd >> 48) & 0xFFFFFFF) as u64;
|
||||
Ok(SdCardInfo {
|
||||
sectors: (c_size + 1) * 1024,
|
||||
})
|
||||
}
|
||||
_ => Err(Error::new("Unknown CSD structure value")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DiskBlock {
|
||||
pub address: u32,
|
||||
pub track: u32,
|
||||
|
@ -28,6 +28,7 @@
|
||||
<menu class="mobile-hidden">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/features.html">Features</a></li>
|
||||
<li><a href="/faq.html">FAQ</a></li>
|
||||
<li><a href="https://menu.summercart64.dev">Menu</a></li>
|
||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
|
||||
<li class="active"><a href="/bom.html">Bill of materials</a></li>
|
||||
@ -39,7 +40,7 @@
|
||||
<iframe class="bom" src="/sc64v2_bom.html"></iframe>
|
||||
|
||||
<footer>
|
||||
<span>© 2020 - 2024 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
|
||||
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
|
247
web/faq.html
Normal file
247
web/faq.html
Normal file
@ -0,0 +1,247 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>SummerCart64 - Features</title>
|
||||
<meta property="og:title" content="SummerCart64 - FAQ" />
|
||||
<meta property="og:description" content="SummerCart64 - a fully open source N64 flashcart" />
|
||||
<meta property="og:url" content="https://summercart64.dev/faq.html" />
|
||||
<meta property="og:image" content="https://summercart64.dev/sc64-embed.png" />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" href="favicon.svg" sizes="any" type="image/svg+xml">
|
||||
<link rel="stylesheet" href="./styles.css">
|
||||
<script src="./script.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="menu-container">
|
||||
<div class="menu-bar">
|
||||
<div class="menu-buttons">
|
||||
<a href="/"><img src="sc64.svg"></a>
|
||||
<div class="menu-button" onclick="showMenu(event)">
|
||||
<div class="menu-button-line"></div>
|
||||
<div class="menu-button-line"></div>
|
||||
<div class="menu-button-line"></div>
|
||||
</div>
|
||||
</div>
|
||||
<menu class="mobile-hidden">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/features.html">Features</a></li>
|
||||
<li class="active"><a href="/faq.html">FAQ</a></li>
|
||||
<li><a href="https://menu.summercart64.dev">Menu</a></li>
|
||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
|
||||
<li><a href="/bom.html">Bill of materials</a></li>
|
||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64">GitHub</a></li>
|
||||
</menu>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-container">
|
||||
<main>
|
||||
<h1>Frequently Asked Questions</h1>
|
||||
<section>
|
||||
<details>
|
||||
<summary>What is the SummerCart64?</summary>
|
||||
<div>
|
||||
<p>SummerCart64 is a device that lets you develop and test games on the N64 console. It is a
|
||||
similar device to the <a href="https://64drive.retroactive.be/">64drive</a>, or <a
|
||||
href="https://krikzz.com/our-products/cartridges/ed64x7.html">EverDrive-64 X7</a>. Its
|
||||
main strengths are fast I/O, developer oriented features, full 64DD emulation and being
|
||||
fully open source.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Is this flashcart for game developers only, or for anyone?</summary>
|
||||
<div>
|
||||
<p>SummerCart64 started its life as a device to aid game development and testing on a real N64
|
||||
console. Over time scope of the project has expanded and currently it's a great choice for
|
||||
casual gamers and people interested in tinkering with the N64.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Where can I get SummerCart64?</summary>
|
||||
<div>
|
||||
<p>For latest information check the <strong>Where to buy</strong> section on the <a
|
||||
href="/">home page</a>.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Which SD cards are supported? Should I format the card in a specific way?</summary>
|
||||
<div>
|
||||
<p>SummerCart64 currently supports cards up to 2 TB in size. Supported filesystems are FAT32 and
|
||||
exFAT. Card can be formatted with any tool but it's recommended to use builtin tools
|
||||
available in your system - for Windows OS just right click on the SD card drive and select
|
||||
<strong>Format</strong> option. For other OS refer to the system's included help.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>I don't care about developer features and just want to play games. How do I setup the SD
|
||||
card?</summary>
|
||||
<div>
|
||||
<p>All necessary information about installing menu and preparing SD card is available on the <a
|
||||
href="https://menu.summercart64.dev">N64FlashcartMenu official website</a>.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>SummerCart64 won't boot and it blinks once every second. What's happening?</summary>
|
||||
<div>
|
||||
<p>Due to technical reasons N64 region lockout can be bypassed only after detecting if the
|
||||
current boot failed. To fix this just power off the console, and then power it on again. If
|
||||
the issue persist then check the next question.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Why is there a battery on the board? Do I need it?</summary>
|
||||
<div>
|
||||
<p>Yes, battery is required for full operation. Battery keeps the time and persistent settings.
|
||||
This includes last detected console region. If your console region is PAL and SummerCart64
|
||||
doesn't boot while LED is blinking once every second then try inserting a battery into the
|
||||
holder.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Which battery model do I need?</summary>
|
||||
<div>
|
||||
<p>Just a standard CR2032 button cell battery.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Do I need an Expansion Pak?</summary>
|
||||
<div>
|
||||
<p>It depends. A common misconception is that the flashcart is able to expand N64 memory. This
|
||||
is technically not possible. The SummerCart64 on its own do not require an Expansion Pak to
|
||||
work. However, for some retail games the Expansion Pak is mandatory to even run the game.
|
||||
Cheat support and most ROM hacks are also dependent on the Expansion Pak.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>I've tried to play a ROM hack / homebrew and it doesn't work. Why?</summary>
|
||||
<div>
|
||||
<p>Most of the ROM hacks available for the N64 absolutely <strong>require</strong> an Expansion
|
||||
Pak to be installed in the console. If the screen stays black after loading the game then
|
||||
either you don't have an Expansion Pak, or the ROM hack is not console compatible. Always
|
||||
verify if the game you're trying to run is designed to run on real hardware, and not just in
|
||||
an emulator.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Can SummerCart64 replace a Controller Pak / Transfer Pak / Rumble Pak?</summary>
|
||||
<div>
|
||||
<p>No. SummerCart64 (and any other thing that is plugged in the cartridge slot) can't replace a
|
||||
device that plugs in the bottom slot of the controller.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Do I need to press the reset button to save the game progress?</summary>
|
||||
<div>
|
||||
<p>No. SummerCart64 watches the console activity and automatically writes save data to the SD
|
||||
card <strong>while you play the game</strong>. Automatic writeback happens about 1 second
|
||||
after game finishes saving the progress.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Where do I get games to play?</summary>
|
||||
<div>
|
||||
<p>You can check entries from the <a href="https://n64brew.dev/wiki/Category:Game_Jams">N64brew
|
||||
game jams</a>. There are plenty of gems to try out!</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>How can I update firmware?</summary>
|
||||
<div>
|
||||
<p>Instructions are available in the <a
|
||||
href="https://github.com/Polprzewodnikowy/SummerCart64/blob/main/docs/00_quick_startup_guide.md#firmware-backupupdate">official
|
||||
documentation</a>.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><code>sc64deployer</code> can't find my cart, why?</summary>
|
||||
<div>
|
||||
<p>On Windows OS please check if you have latest FTDI drivers installed, either through Windows
|
||||
Update or directly from <a href="https://ftdichip.com/drivers/vcp-drivers">chip
|
||||
manufacturer's website</a>. On Linux OS check if you have proper permissions to the USB
|
||||
device.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Can I use software other than <code>sc64deployer</code>?</summary>
|
||||
<div>
|
||||
<p>Yes. <a href="https://github.com/buu342/N64-UNFLoader">UNFLoader</a> is also supported.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Real time clock is not keeping the time, what's wrong?</summary>
|
||||
<div>
|
||||
<p>Make sure that the battery is installed in the correct orientation. Don't forget to peel all
|
||||
protective plastic stickers from the battery before installation.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>What is the purpose for button on the back?</summary>
|
||||
<div>
|
||||
<p>It has several uses:</p>
|
||||
<ol>
|
||||
<li>To eject/swap 64DD disks - when there are multiple virtual 64DD disks "inserted".</li>
|
||||
<li>To run the built-in test ROM - hold the button while switching the power on.</li>
|
||||
<li>For game developers as additional input.</li>
|
||||
</ol>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Can I access files on the microSD card via USB port on the PC?</summary>
|
||||
<div>
|
||||
<p>Yes. <code>sc64deployer</code> has a <code>sd</code> command to access files on the SD card.
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Does SummerCart64 come with a warranty?</summary>
|
||||
<div>
|
||||
<p>As a DIY product and an open-source project, The SummerCart64 (design/software and any other
|
||||
asset contained as part of this project) have no warranty or liability whatsoever. For any
|
||||
product that is distributed from an online or retail store, please seek warranty and/or help
|
||||
(as required by your country law) from them, not from the developers of the SummerCart64
|
||||
project.</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>I bought SummerCart64 from XXX store. It doesn't work or there are weird problems. What
|
||||
should I do?</summary>
|
||||
<div>
|
||||
<p>Always contact the seller you've bought the device to resolve the issues. Developers and
|
||||
community are not responsible for low quality hardware and lack of quality control.</p>
|
||||
</div>
|
||||
</details>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -28,6 +28,7 @@
|
||||
<menu class="mobile-hidden">
|
||||
<li><a href="/">Home</a></li>
|
||||
<li class="active"><a href="/features.html">Features</a></li>
|
||||
<li><a href="/faq.html">FAQ</a></li>
|
||||
<li><a href="https://menu.summercart64.dev">Menu</a></li>
|
||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
|
||||
<li><a href="/bom.html">Bill of materials</a></li>
|
||||
@ -181,7 +182,7 @@
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>© 2020 - 2024 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
|
||||
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
|
@ -28,6 +28,7 @@
|
||||
<menu class="mobile-hidden">
|
||||
<li class="active"><a href="/">Home</a></li>
|
||||
<li><a href="/features.html">Features</a></li>
|
||||
<li><a href="/faq.html">FAQ</a></li>
|
||||
<li><a href="https://menu.summercart64.dev">Menu</a></li>
|
||||
<li><a href="https://github.com/Polprzewodnikowy/SummerCart64/releases/latest">Downloads</a></li>
|
||||
<li><a href="/bom.html">Bill of materials</a></li>
|
||||
@ -38,12 +39,12 @@
|
||||
|
||||
<div class="main-container">
|
||||
<main>
|
||||
<h1>What is SummerCart64?</h1>
|
||||
<h1>What is the SummerCart64?</h1>
|
||||
<section>
|
||||
<img class="sc64-logo" src="sc64.svg">
|
||||
<p>SummerCart64 is a custom made cartridge (commonly referred as a flashcart) that allows you to develop
|
||||
and play games on the N64 console. It is the first N64 flashcart that is feature complete and
|
||||
completely open source.</p>
|
||||
and play games on the N64 console. It is the first N64 flashcart that is feature complete and, at
|
||||
the same time, completely open source.</p>
|
||||
<p>With fast I/O, integrated support in <a href="https://libdragon.dev">libdragon</a> / <a
|
||||
href="https://github.com/devwizard64/libcart">libcart</a> / <a
|
||||
href="https://github.com/buu342/N64-UNFLoader">UNFLoader</a>, and well documented API, testing
|
||||
@ -71,7 +72,9 @@
|
||||
check the <code><strong>#sc64-forum</strong></code> channel.</li>
|
||||
<li><a href="https://www.pcbway.com/project/member/shareproject/?bmbno=1046ED64-8AEE-44">PCBWay
|
||||
Shared Project</a> page - both assembled boards and bare PCBs are available.</li>
|
||||
<li>Manual PCB and components order - DIY friendly option.</li>
|
||||
<li>Manual PCB and components order - DIY friendly option. For more details check out the <a
|
||||
href="https://github.com/Polprzewodnikowy/SummerCart64/blob/main/docs/06_build_guide.md">build
|
||||
guide</a>.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@ -94,13 +97,10 @@
|
||||
|
||||
<h1>Support the project</h1>
|
||||
<section>
|
||||
<p>Like the project? Consider supporting it on the <a
|
||||
href="https://github.com/sponsors/Polprzewodnikowy">GitHub Sponsors</a>, <a
|
||||
href="https://ko-fi.com/polprzewodnikowy">Ko-fi</a>, or click the buttons below. Not necessary
|
||||
but greatly appreciated.</p>
|
||||
<p>Like the project? Consider supporting the effort on <a
|
||||
href="https://ko-fi.com/polprzewodnikowy">Ko-fi</a>, or click a button below. Not necessary
|
||||
but greatly appreciated. Tips are non-refundable, support responsibly.</p>
|
||||
<div class="sponsor-buttons">
|
||||
<iframe src="https://github.com/sponsors/Polprzewodnikowy/button" title="Sponsor Polprzewodnikowy"
|
||||
height="32" width="114" style="border: 0; border-radius: 6px;"></iframe>
|
||||
<a href='https://ko-fi.com/R5R13I07C' target='_blank'><img height='32'
|
||||
src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' alt='Buy Me a Coffee at ko-fi.com' /></a>
|
||||
</div>
|
||||
@ -109,7 +109,7 @@
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<span>© 2020 - 2024 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
|
||||
<span>© 2020 - 2025 <a href="https://mateuszfaderewski.pl">Mateusz Faderewski</a></span>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
|
@ -23,6 +23,8 @@
|
||||
|
||||
--sponsor-buttons-height: 32px;
|
||||
|
||||
--details-bg-color: rgb(56, 56, 128);
|
||||
|
||||
--footer-padding: 16px 32px 16px 32px;
|
||||
--footer-font-size: 14px;
|
||||
}
|
||||
@ -57,6 +59,7 @@ a:visited {
|
||||
a:active,
|
||||
a:hover {
|
||||
color: var(--text-color);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.menu-container {
|
||||
@ -259,6 +262,22 @@ main .separator {
|
||||
margin: var(--main-separator-margin) 0;
|
||||
}
|
||||
|
||||
details {
|
||||
margin: var(--main-paragraph-margin) 0;
|
||||
}
|
||||
|
||||
details summary {
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
details > div {
|
||||
overflow: auto;
|
||||
margin-top: var(--main-paragraph-margin);
|
||||
padding: 0 var(--main-paragraph-margin);
|
||||
background-color: var(--details-bg-color);
|
||||
}
|
||||
|
||||
iframe.bom {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user