shuffle some bits

This commit is contained in:
Mateusz Faderewski 2024-08-10 00:50:12 +02:00
parent 598a4205bb
commit 9f9c3fc19c
6 changed files with 206 additions and 160 deletions

View File

@ -1,21 +1,21 @@
- [Internal memory map](#internal-memory-map) - [Internal memory map](#internal-memory-map)
- [PI memory map](#pi-memory-map) - [PI memory map](#pi-memory-map)
- [Address decoding limitations](#address-decoding-limitations) - [Address decoding limitations](#address-decoding-limitations)
- [Flash mapped sections](#flash-mapped-sections) - [Flash mapped sections](#flash-mapped-sections)
- [SC64 registers](#sc64-registers) - [SC64 registers](#sc64-registers)
- [`0x1FFF_0000`: **STATUS/COMMAND**](#0x1fff_0000-statuscommand) - [`0x1FFF_0000`: **SCR**](#0x1fff_0000-scr)
- [`0x1FFF_0004`: **DATA0** and `0x1FFF_0008`: **DATA1**](#0x1fff_0004-data0-and-0x1fff_0008-data1) - [`0x1FFF_0004`: **DATA0** / `0x1FFF_0008`: **DATA1**](#0x1fff_0004-data0--0x1fff_0008-data1)
- [`0x1FFF_000C`: **IDENTIFIER**](#0x1fff_000c-identifier) - [`0x1FFF_000C`: **IDENTIFIER**](#0x1fff_000c-identifier)
- [`0x1FFF_0010`: **KEY**](#0x1fff_0010-key) - [`0x1FFF_0010`: **KEY**](#0x1fff_0010-key)
- [`0x1FFF_0014`: **IRQ**](#0x1fff_0014-irq) - [`0x1FFF_0014`: **IRQ**](#0x1fff_0014-irq)
- [`0x1FFF_0018`: **AUX**](#0x1fff_0018-aux) - [`0x1FFF_0018`: **AUX**](#0x1fff_0018-aux)
- [Command execution flow](#command-execution-flow) - [Command execution flow](#command-execution-flow)
- [Without interrupt](#without-interrupt) - [Without interrupt](#without-interrupt)
- [With interrupt](#with-interrupt) - [With interrupt](#with-interrupt)
---
## Internal memory map
# Internal memory map
This mapping is used internally by FPGA/μC and when accessing flashcart from USB side. This mapping is used internally by FPGA/μC and when accessing flashcart from USB side.
@ -33,9 +33,9 @@ This mapping is used internally by FPGA/μC and when accessing flashcart from US
- Note [2]: Due to BlockRAM usage optimization this section is read only. - Note [2]: Due to BlockRAM usage optimization this section is read only.
- Note [3]: Read returns `0`. Maximum accessible address space is 128 MiB. - Note [3]: Read returns `0`. Maximum accessible address space is 128 MiB.
---
## PI memory map
# PI memory map
This mapping is used when accessing flashcart from N64 side. This mapping is used when accessing flashcart from N64 side.
@ -84,55 +84,60 @@ Special commands are provided for performing flash erase and program.
During those operations avoid accessing flash mapped sections. During those operations avoid accessing flash mapped sections.
Data read will be corrupted and erase/program operations slows down. Data read will be corrupted and erase/program operations slows down.
---
## SC64 registers
# SC64 registers
SC64 contains small register region used for communication between N64 and controller code running on the μC. SC64 contains small register region used for communication between N64 and controller code running on the μC.
Protocol is command based with support for up to 256 different commands and two 32-bit argument/result values per operation. Protocol is command based with support for up to 256 different commands and two 32-bit argument/result values per operation.
Support for interrupts is provided but currently no command relies on it, 64DD IRQ is handled separately. Command execution finish can be optionally signaled with "cart interrupt"
| name | address | size | access | usage | | name | address | size | access | usage |
| ------------------ | ------------- | ------- | ------ | -------------------------------- | | -------------- | ------------- | ------- | ------ | -------------------------------- |
| **STATUS/COMMAND** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status | | **SCR** | `0x1FFF_0000` | 4 bytes | RW | Command execution and status |
| **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 | | **DATA0** | `0x1FFF_0004` | 4 bytes | RW | Command argument/result 0 |
| **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 | | **DATA1** | `0x1FFF_0008` | 4 bytes | RW | Command argument/result 1 |
| **IDENTIFIER** | `0x1FFF_000C` | 4 bytes | RW | Flashcart identifier | | **IDENTIFIER** | `0x1FFF_000C` | 4 bytes | RW | Flashcart identifier |
| **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock | | **KEY** | `0x1FFF_0010` | 4 bytes | W | SC64 register access lock/unlock |
| **IRQ** | `0x1FFF_0014` | 4 bytes | RW | IRQ clear and enable | | **IRQ** | `0x1FFF_0014` | 4 bytes | W | IRQ clear and enable |
| **AUX** | `0x1FFF_0018` | 4 bytes | RW | Auxiliary interrupt data channel | | **AUX** | `0x1FFF_0018` | 4 bytes | RW | Auxiliary interrupt data channel |
--- ---
#### `0x1FFF_0000`: **STATUS/COMMAND** ### `0x1FFF_0000`: **SCR**
| name | bits | access | meaning | | name | bits | access | meaning |
| ----------------- | ------ | ------ | ----------------------------------------------------------- | | ----------------- | ------ | ------ | ------------------------------------------------------------ |
| `CMD_BUSY` | [31] | R | `1` if dispatched command is pending/executing | | `CMD_BUSY` | [31] | R | `1` if dispatched command is pending/executing |
| `CMD_ERROR` | [30] | R | `1` if last executed command returned with error code | | `CMD_ERROR` | [30] | R | `1` if last executed command returned with error code |
| `BTN_IRQ_PENDING` | [29] | R | `1` if flashcart has raised a "button pressed" interrupt | | `BTN_IRQ_PENDING` | [29] | R | `1` if flashcart has raised a "button pressed" interrupt |
| `CMD_IRQ_PENDING` | [28] | R | `1` if flashcart has raised a "command finish" interrupt | | `BTN_IRQ_MASK` | [28] | R | `1` means "button pressed" interrupt is enabled (always `1`) |
| `USB_IRQ_PENDING` | [27] | R | `1` if flashcart has raised au "USB not empty" interrupt | | `CMD_IRQ_PENDING` | [27] | R | `1` if flashcart has raised a "command finish" interrupt |
| `AUX_IRQ_PENDING` | [26] | R | `1` if flashcart has raised an "AUX not empty" interrupt | | `CMD_IRQ_MASK` | [26] | R | `1` means "command finish" interrupt is enabled (always `1`) |
| N/A | [25:9] | N/A | Unused, write `0` for future compatibility | | `USB_IRQ_PENDING` | [25] | R | `1` if flashcart has raised an "USB not empty" interrupt |
| `CMD_IRQ_REQUEST` | [8] | RW | Raise cart interrupt signal when command finishes execution | | `USB_IRQ_MASK` | [24] | R | `1` means "USB not empty" interrupt is enabled |
| `CMD_ID` | [7:0] | RW | Command ID to be executed | | `AUX_IRQ_PENDING` | [23] | R | `1` if flashcart has raised an "AUX not empty" interrupt |
| `AUX_IRQ_MASK` | [22] | R | `1` means "AUX not empty" interrupt is enabled |
| N/A | [21:9] | N/A | Unused, write `0` for future compatibility |
| `CMD_IRQ_REQUEST` | [8] | RW | Raise cart interrupt signal when command finishes execution |
| `CMD_ID` | [7:0] | RW | Command ID to be executed |
Note: Write to this register raises `CMD_BUSY` bit and clears `CMD_ERROR` bit. Flashcart then will start executing provided command. Note: Write to this register raises `CMD_BUSY` bit and clears `CMD_ERROR` bit. Flashcart then will start executing provided command.
--- ---
#### `0x1FFF_0004`: **DATA0** and `0x1FFF_0008`: **DATA1** ### `0x1FFF_0004`: **DATA0** / `0x1FFF_0008`: **DATA1**
| name | bits | access | meaning | | name | bits | access | meaning |
| --------- | ------ | ------ | ---------------------------------- | | --------- | ------ | ------ | ---------------------------------- |
| `DATA0/1` | [31:0] | RW | Command argument (W) or result (R) | | `DATA0/1` | [31:0] | RW | Command argument (W) or result (R) |
Note: Result is valid only when command has executed and `CMD_BUSY` bit is reset. When `CMD_ERROR` is set then **DATA0** register contains error code. Note: Result is valid only when command has finished execution and `CMD_BUSY` bit is reset.
When `CMD_ERROR` is set then **DATA0** register contains error code.
--- ---
#### `0x1FFF_000C`: **IDENTIFIER** ### `0x1FFF_000C`: **IDENTIFIER**
| name | bits | access | meaning | | name | bits | access | meaning |
| ------------ | ------ | ------ | ----------------------------------- | | ------------ | ------ | ------ | ----------------------------------- |
@ -141,7 +146,7 @@ Note: Result is valid only when command has executed and `CMD_BUSY` bit is reset
--- ---
#### `0x1FFF_0010`: **KEY** ### `0x1FFF_0010`: **KEY**
| name | bits | access | meaning | | name | bits | access | meaning |
| ----- | ------ | ------ | ------------------------------------------ | | ----- | ------ | ------ | ------------------------------------------ |
@ -156,40 +161,37 @@ Value `0xFFFFFFFF` will lock all SC64 registers if flashcart is in unlock state.
--- ---
#### `0x1FFF_0014`: **IRQ** ### `0x1FFF_0014`: **IRQ**
| name | bits | access | meaning | | name | bits | access | meaning |
| ----------------- | ------- | ------ | ----------------------------------------------------------------- | | ----------------- | ------- | ------ | ---------------------------------------------- |
| `BTN_CLEAR` | [31] | W | Write `1` to clear "button pressed" interrupt | | `BTN_CLEAR` | [31] | W | Write `1` to clear "button pressed" interrupt |
| `CMD_CLEAR` | [30] | W | Write `1` to clear "command finish" interrupt | | `CMD_CLEAR` | [30] | W | Write `1` to clear "command finish" interrupt |
| `USB_CLEAR` | [29] | W | Write `1` to clear "USB not empty" interrupt | | `USB_CLEAR` | [29] | W | Write `1` to clear "USB not empty" interrupt |
| `AUX_CLEAR` | [28] | W | Write `1` to clear "AUX not empty" interrupt | | `AUX_CLEAR` | [28] | W | Write `1` to clear "AUX not empty" interrupt |
| N/A | [27:24] | N/A | Unused, write `0` for future compatibility | | N/A | [27:12] | N/A | Unused, write `0` for future compatibility |
| `MCU_IRQ_MASK` | [23] | R | `1` means "button pressed" interrupt is enabled (it's always `1`) | | `USB_IRQ_DISABLE` | [11] | W | Write `1` to disable "USB not empty" interrupt |
| `CMD_IRQ_MASK` | [22] | R | `1` means "command finish" interrupt is enabled (it's always `1`) | | `USB_IRQ_ENABLE` | [10] | W | Write `1` to enable "USB not empty" interrupt |
| `USB_IRQ_MASK` | [21] | R | `1` means "USB not empty" interrupt is enabled | | `AUX_IRQ_DISABLE` | [9] | W | Write `1` to disable "AUX not empty" interrupt |
| `AUX_IRQ_MASK` | [20] | R | `1` means "AUX not empty" interrupt is enabled | | `AUX_IRQ_ENABLE` | [8] | W | Write `1` to enable "AUX not empty" interrupt |
| N/A | [19:16] | N/A | Unused, write `0` for future compatibility | | N/A | [7:0] | N/A | Unused, write `0` for future compatibility |
| `USB_IRQ_DISABLE` | [11] | W | Write `1` to disable "USB not empty" interrupt |
| `USB_IRQ_ENABLE` | [10] | W | Write `1` to enable "USB not empty" interrupt |
| `AUX_IRQ_DISABLE` | [9] | W | Write `1` to disable "AUX not empty" interrupt |
| `AUX_IRQ_ENABLE` | [8] | W | Write `1` to enable "AUX not empty" interrupt |
| N/A | [7:0] | N/A | Unused, write `0` for future compatibility |
Note: All interrupts are cleared and disabled when any of the following events occur: Note: All interrupts are cleared and disabled when any of the following events occur:
- Hard reset - Hard reset
- NMI reset - NMI reset
- SC64 registers locking - SC64 registers lock
SC64 interrupts are completely disabled when register access is not enabled. SC64 interrupts are completely disabled when register access is not enabled.
--- ---
#### `0x1FFF_0018`: **AUX** ### `0x1FFF_0018`: **AUX**
This register can be used as a very simple interface to the PC via USB. This register can be used as a very simple interface to the PC via USB.
Writing to this register generates an USB transfer with the contents of the written data. Writing to this register generates an USB transfer with the contents of the written data.
New data available in the register is signaled via cart interrupt that needs to be enabled beforehand in the `IRQ` register. New data available in the register are signaled via cart interrupt that needs to be enabled beforehand by setting `AUX_IRQ_ENABLE` bit in the **IRQ** register.
Status can be also manually polled by checking `AUX_IRQ_PENDING` bit in **SCR** register.
Interrupt needs to be acknowledged by setting `AUX_CLEAR` bit in the **IRQ** register.
There is no flow control, use this register as a ping-pong interface. There is no flow control, use this register as a ping-pong interface.
For example, PC sends AUX data, N64 receives interrupt, reads the data then writes to this register with a response. For example, PC sends AUX data, N64 receives interrupt, reads the data then writes to this register with a response.
This flow can be reversed if needed - N64 can be the initiating side. This flow can be reversed if needed - N64 can be the initiating side.
@ -198,28 +200,39 @@ This flow can be reversed if needed - N64 can be the initiating side.
| ------ | ------ | ------ | -------------- | | ------ | ------ | ------ | -------------- |
| `DATA` | [31:0] | RW | Arbitrary data | | `DATA` | [31:0] | RW | Arbitrary data |
--- This register is used by the upload process in the `sc64deployer` to notify running app on the N64 about certain events.
All `DATA` values with upper 8 bits set to `1` (`0xFFxxxxxx`) are reserved for internal use by the SC64.
Refrain from using these values in your app for uses other than listed below.
Currently defined reserved `DATA` values are:
## Command execution flow - `0xFF000001` - **IO Halt** - causes the running app to stop all cartridge IO activity (PI bus and Joybus) in preparation for uploading new ROM to the SC64.
App still should listen to the AUX interrupt and respond to other messages.
- `0xFF000002` - **Reboot** - causes the running app to perform soft reset by reloading IPL3 from the ROM and start executing it.
App running on the N64 shall respond to the AUX message with the same `DATA` value as incoming event for all events listed above, unless it's specified otherwise.
# Command execution flow
### Without interrupt ### Without interrupt
1. Check if command is already executing by reading `CMD_BUSY` bit in **STATUS/COMMAND** register (optional). 1. Check if command is already executing by reading `CMD_BUSY` bit in **SCR** register (optional).
2. Write command argument values to **DATA0** and **DATA1** registers, can be skipped if command doesn't require it. 2. Write command argument values to **DATA0** and **DATA1** registers, can be skipped if command doesn't require it.
3. Write command ID to **STATUS/COMMAND** register. 3. Write command ID to **SCR** register.
4. Wait for `CMD_BUSY` bit in **STATUS/COMMAND** register to go low. 4. Wait for `CMD_BUSY` bit in **SCR** register to go low.
5. Check if `CMD_ERROR` bit in **STATUS/COMMAND** is set: 5. Check if `CMD_ERROR` bit in **SCR** is set:
- If error is set then read **DATA0** register containing error code. - If error is set then read **DATA0** register containing error code.
- If error is not set then read **DATA0** and **DATA1** registers containing command result values, can be skipped if command doesn't return any values. - If error is not set then read **DATA0** and **DATA1** registers containing command result values, can be skipped if command doesn't return any values.
### With interrupt ### With interrupt
1. Check if command is already executing by reading `CMD_BUSY` bit in **STATUS/COMMAND** register (optional). 1. Check if command is already executing by reading `CMD_BUSY` bit in **SCR** register (optional).
2. Write command argument values to **DATA0** and **DATA1** registers, can be skipped if command doesn't require it. 2. Write command argument values to **DATA0** and **DATA1** registers, can be skipped if command doesn't require it.
3. Write command ID to **STATUS/COMMAND** register and set `CMD_IRQ_REQUEST` bit high. 3. Write command ID to **SCR** register and set `CMD_IRQ_REQUEST` bit high.
4. Wait for cart interrupt. 4. Wait for cart interrupt.
5. Check (in cart interrupt handler) if `CMD_IRQ_PENDING` bit in **STATUS/COMMAND** register is set high. 5. Check (in cart interrupt handler) if `CMD_IRQ_PENDING` bit in **SCR** register is set high.
6. Clear interrupt by setting `CMD_CLEAR` bit high in the **IRQ** register. 6. Clear interrupt by setting `CMD_CLEAR` bit high in the **IRQ** register.
7. Check if `CMD_ERROR` bit in **STATUS/COMMAND** is set: 7. Check if `CMD_ERROR` bit in **SCR** is set:
- If error is set then read **DATA0** register containing error code. - If error is set then read **DATA0** register containing error code.
- If error is not set then read **DATA0** and **DATA1** registers containing command result values, can be skipped if command doesn't return any values. - If error is not set then read **DATA0** and **DATA1** registers containing command result values, can be skipped if command doesn't return any values.

View File

@ -34,18 +34,25 @@ module n64_cfg (
logic usb_irq; logic usb_irq;
logic aux_irq; logic aux_irq;
logic usb_irq_enabled; logic btn_irq_mask;
logic aux_irq_enabled; logic cmd_irq_mask;
logic usb_irq_mask;
logic aux_irq_mask;
always_ff @(posedge clk) begin always_ff @(posedge clk) begin
irq <= ( irq <= (
cmd_irq || (btn_irq && btn_irq_mask) ||
btn_irq || (cmd_irq && cmd_irq_mask) ||
(usb_irq_enabled && usb_irq) || (usb_irq && usb_irq_mask) ||
(aux_irq_enabled && aux_irq) (aux_irq && aux_irq_mask)
); );
end end
always_comb begin
btn_irq_mask = 1'b1;
cmd_irq_mask = 1'b1;
end
always_comb begin always_comb begin
reg_bus.rdata = 16'd0; reg_bus.rdata = 16'd0;
if (reg_bus.address[16] && (reg_bus.address[15:5] == 11'd0)) begin if (reg_bus.address[16] && (reg_bus.address[15:5] == 11'd0)) begin
@ -54,10 +61,14 @@ module n64_cfg (
n64_scb.cfg_pending, n64_scb.cfg_pending,
cmd_error, cmd_error,
btn_irq, btn_irq,
btn_irq_mask,
cmd_irq, cmd_irq,
cmd_irq_mask,
usb_irq, usb_irq,
usb_irq_mask,
aux_irq, aux_irq,
10'd0 aux_irq_mask,
6'd0
}; };
REG_COMMAND: reg_bus.rdata = {7'd0, cmd_irq_request, n64_scb.cfg_cmd}; REG_COMMAND: reg_bus.rdata = {7'd0, cmd_irq_request, n64_scb.cfg_cmd};
REG_DATA_0_H: reg_bus.rdata = n64_scb.cfg_wdata[0][31:16]; REG_DATA_0_H: reg_bus.rdata = n64_scb.cfg_wdata[0][31:16];
@ -68,14 +79,7 @@ module n64_cfg (
REG_IDENTIFIER_L: reg_bus.rdata = n64_scb.cfg_identifier[15:0]; REG_IDENTIFIER_L: reg_bus.rdata = n64_scb.cfg_identifier[15:0];
REG_KEY_H: reg_bus.rdata = 16'd0; REG_KEY_H: reg_bus.rdata = 16'd0;
REG_KEY_L: reg_bus.rdata = 16'd0; REG_KEY_L: reg_bus.rdata = 16'd0;
REG_IRQ_H: reg_bus.rdata = { REG_IRQ_H: reg_bus.rdata = 16'd0;
8'd0,
1'b1,
1'b1,
usb_irq_enabled,
aux_irq_enabled,
4'd0
};
REG_IRQ_L: reg_bus.rdata = 16'd0; REG_IRQ_L: reg_bus.rdata = 16'd0;
REG_AUX_H: reg_bus.rdata = n64_scb.aux_wdata[31:16]; REG_AUX_H: reg_bus.rdata = n64_scb.aux_wdata[31:16];
REG_AUX_L: reg_bus.rdata = n64_scb.aux_wdata[15:0]; REG_AUX_L: reg_bus.rdata = n64_scb.aux_wdata[15:0];
@ -119,22 +123,22 @@ module n64_cfg (
n64_scb.cfg_cmd <= 8'h00; n64_scb.cfg_cmd <= 8'h00;
cmd_error <= 1'b0; cmd_error <= 1'b0;
cmd_irq_request <= 1'b0; cmd_irq_request <= 1'b0;
cmd_irq <= 1'b0;
btn_irq <= 1'b0; btn_irq <= 1'b0;
cmd_irq <= 1'b0;
usb_irq <= 1'b0; usb_irq <= 1'b0;
aux_irq <= 1'b0; aux_irq <= 1'b0;
usb_irq_enabled <= 1'b0; usb_irq_mask <= 1'b0;
aux_irq_enabled <= 1'b0; aux_irq_mask <= 1'b0;
lock_sequence_counter <= 1'd0; lock_sequence_counter <= 1'd0;
end else if (n64_scb.n64_reset || n64_scb.n64_nmi) begin end else if (n64_scb.n64_reset || n64_scb.n64_nmi) begin
n64_scb.cfg_unlock <= 1'b0; n64_scb.cfg_unlock <= 1'b0;
cmd_irq_request <= 1'b0; cmd_irq_request <= 1'b0;
cmd_irq <= 1'b0;
btn_irq <= 1'b0; btn_irq <= 1'b0;
cmd_irq <= 1'b0;
usb_irq <= 1'b0; usb_irq <= 1'b0;
aux_irq <= 1'b0; aux_irq <= 1'b0;
usb_irq_enabled <= 1'b0; usb_irq_mask <= 1'b0;
aux_irq_enabled <= 1'b0; aux_irq_mask <= 1'b0;
lock_sequence_counter <= 1'd0; lock_sequence_counter <= 1'd0;
end else if (n64_scb.cfg_unlock) begin end else if (n64_scb.cfg_unlock) begin
if (reg_bus.write && reg_bus.address[16] && (reg_bus.address[15:5] == 11'd0)) begin if (reg_bus.write && reg_bus.address[16] && (reg_bus.address[15:5] == 11'd0)) begin
@ -185,12 +189,12 @@ module n64_cfg (
if (reg_bus.wdata == 16'hFFFF) begin if (reg_bus.wdata == 16'hFFFF) begin
n64_scb.cfg_unlock <= 1'b0; n64_scb.cfg_unlock <= 1'b0;
cmd_irq_request <= 1'b0; cmd_irq_request <= 1'b0;
cmd_irq <= 1'b0;
btn_irq <= 1'b0; btn_irq <= 1'b0;
cmd_irq <= 1'b0;
usb_irq <= 1'b0; usb_irq <= 1'b0;
aux_irq <= 1'b0; aux_irq <= 1'b0;
usb_irq_enabled <= 1'b0; usb_irq_mask <= 1'b0;
aux_irq_enabled <= 1'b0; aux_irq_mask <= 1'b0;
end end
end end
end end
@ -203,17 +207,18 @@ module n64_cfg (
end end
REG_IRQ_L: begin REG_IRQ_L: begin
if (reg_bus.wdata[11]) begin
usb_irq_enabled <= 1'b0;
end
if (reg_bus.wdata[10]) begin if (reg_bus.wdata[10]) begin
usb_irq_enabled <= 1'b1; usb_irq_mask <= 1'b1;
end
if (reg_bus.wdata[11]) begin
usb_irq_mask <= 1'b0;
end
if (reg_bus.wdata[8]) begin
aux_irq_mask <= 1'b1;
end end
if (reg_bus.wdata[9]) begin if (reg_bus.wdata[9]) begin
aux_irq_enabled <= 1'b0; aux_irq_mask <= 1'b0;
end
if (reg_bus.wdata[8]) begin
aux_irq_enabled <= 1'b1;
end end
end end

View File

@ -4,7 +4,7 @@
typedef struct { typedef struct {
io32_t SR_CMD; io32_t SCR;
io32_t DATA[2]; io32_t DATA[2];
io32_t IDENTIFIER; io32_t IDENTIFIER;
io32_t KEY; io32_t KEY;
@ -16,13 +16,17 @@ typedef struct {
#define SC64_REGS ((sc64_regs_t *) SC64_REGS_BASE) #define SC64_REGS ((sc64_regs_t *) SC64_REGS_BASE)
#define SC64_SR_CMD_IRQ_REQUEST (1 << 8) #define SC64_SCR_CPU_BUSY (1 << 31)
#define SC64_SR_AUX_IRQ_PENDING (1 << 26) #define SC64_SCR_CMD_ERROR (1 << 30)
#define SC64_SR_USB_IRQ_PENDING (1 << 27) #define SC64_SCR_BTN_IRQ_PENDING (1 << 29)
#define SC64_SR_CMD_IRQ_PENDING (1 << 28) #define SC64_SCR_BTN_IRQ_MASK (1 << 28)
#define SC64_SR_BTN_IRQ_PENDING (1 << 29) #define SC64_SCR_CMD_IRQ_PENDING (1 << 27)
#define SC64_SR_CMD_ERROR (1 << 30) #define SC64_SCR_CMD_IRQ_MASK (1 << 26)
#define SC64_SR_CPU_BUSY (1 << 31) #define SC64_SCR_USB_IRQ_PENDING (1 << 25)
#define SC64_SCR_USB_IRQ_MASK (1 << 24)
#define SC64_SCR_AUX_IRQ_PENDING (1 << 23)
#define SC64_SCR_AUX_IRQ_MASK (1 << 22)
#define SC64_SCR_CMD_IRQ_REQUEST (1 << 8)
#define SC64_V2_IDENTIFIER (0x53437632) #define SC64_V2_IDENTIFIER (0x53437632)
@ -31,20 +35,14 @@ typedef struct {
#define SC64_KEY_UNLOCK_2 (0x4F434B5FUL) #define SC64_KEY_UNLOCK_2 (0x4F434B5FUL)
#define SC64_KEY_LOCK (0xFFFFFFFFUL) #define SC64_KEY_LOCK (0xFFFFFFFFUL)
#define SC64_IRQ_AUX_ENABLE (1 << 8)
#define SC64_IRQ_AUX_DISABLE (1 << 9)
#define SC64_IRQ_USB_ENABLE (1 << 10)
#define SC64_IRQ_USB_DISABLE (1 << 11)
#define SC64_IRQ_AUX_MASK (1 << 20)
#define SC64_IRQ_USB_MASK (1 << 21)
#define SC64_IRQ_CMD_MASK (1 << 22)
#define SC64_IRQ_BTN_MASK (1 << 23)
#define SC64_IRQ_AUX_CLEAR (1 << 28)
#define SC64_IRQ_USB_CLEAR (1 << 29)
#define SC64_IRQ_CMD_CLEAR (1 << 30)
#define SC64_IRQ_BTN_CLEAR (1 << 31) #define SC64_IRQ_BTN_CLEAR (1 << 31)
#define SC64_IRQ_CMD_CLEAR (1 << 30)
#define SC64_IRQ_USB_CLEAR (1 << 29)
#define SC64_IRQ_AUX_CLEAR (1 << 28)
#define SC64_IRQ_USB_DISABLE (1 << 11)
#define SC64_IRQ_USB_ENABLE (1 << 10)
#define SC64_IRQ_AUX_DISABLE (1 << 9)
#define SC64_IRQ_AUX_ENABLE (1 << 8)
typedef enum { typedef enum {
@ -101,20 +99,20 @@ static sc64_error_t sc64_execute_cmd (sc64_cmd_t *cmd) {
if (use_cmd_irq) { if (use_cmd_irq) {
wait_cmd_irq = true; wait_cmd_irq = true;
pi_io_write(&SC64_REGS->SR_CMD, (SC64_SR_CMD_IRQ_REQUEST | (cmd->id & 0xFF))); pi_io_write(&SC64_REGS->SCR, (SC64_SCR_CMD_IRQ_REQUEST | (cmd->id & 0xFF)));
while (wait_cmd_irq); while (wait_cmd_irq);
sr = pi_io_read(&SC64_REGS->SR_CMD); sr = pi_io_read(&SC64_REGS->SCR);
if (sr & SC64_SR_CPU_BUSY) { if (sr & SC64_SCR_CPU_BUSY) {
error_display("[Unexpected] SC64 CMD busy flag set"); error_display("[Unexpected] SC64 CMD busy flag set");
} }
} else { } else {
pi_io_write(&SC64_REGS->SR_CMD, (cmd->id & 0xFF)); pi_io_write(&SC64_REGS->SCR, (cmd->id & 0xFF));
do { do {
sr = pi_io_read(&SC64_REGS->SR_CMD); sr = pi_io_read(&SC64_REGS->SCR);
} while (sr & SC64_SR_CPU_BUSY); } while (sr & SC64_SCR_CPU_BUSY);
} }
if (sr & SC64_SR_CMD_ERROR) { if (sr & SC64_SCR_CMD_ERROR) {
return (sc64_error_t) (pi_io_read(&SC64_REGS->DATA[0])); return (sc64_error_t) (pi_io_read(&SC64_REGS->DATA[0]));
} }
@ -218,7 +216,7 @@ void sc64_lock (void) {
bool sc64_check_presence (void) { bool sc64_check_presence (void) {
bool detected = (pi_io_read(&SC64_REGS->IDENTIFIER) == SC64_V2_IDENTIFIER); bool detected = (pi_io_read(&SC64_REGS->IDENTIFIER) == SC64_V2_IDENTIFIER);
if (detected) { if (detected) {
while (pi_io_read(&SC64_REGS->SR_CMD) & SC64_SR_CPU_BUSY); while (pi_io_read(&SC64_REGS->SCR) & SC64_SCR_CPU_BUSY);
} }
return detected; return detected;
} }
@ -237,18 +235,18 @@ void sc64_aux_irq_enable (bool enable) {
} }
sc64_irq_t sc64_irq_pending (void) { sc64_irq_t sc64_irq_pending (void) {
uint32_t sr = pi_io_read(&SC64_REGS->SR_CMD); uint32_t sr = pi_io_read(&SC64_REGS->SCR);
sc64_irq_t irq = SC64_IRQ_NONE; sc64_irq_t irq = SC64_IRQ_NONE;
if (sr & SC64_SR_BTN_IRQ_PENDING) { if ((sr & SC64_SCR_BTN_IRQ_MASK) && (sr & SC64_SCR_BTN_IRQ_PENDING)) {
irq |= SC64_IRQ_BTN; irq |= SC64_IRQ_BTN;
} }
if (sr & SC64_SR_CMD_IRQ_PENDING) { if ((sr & SC64_SCR_CMD_IRQ_MASK) && (sr & SC64_SCR_CMD_IRQ_PENDING)) {
irq |= SC64_IRQ_CMD; irq |= SC64_IRQ_CMD;
} }
if (sr & SC64_SR_USB_IRQ_PENDING) { if ((sr & SC64_SCR_USB_IRQ_MASK) && (sr & SC64_SCR_USB_IRQ_PENDING)) {
irq |= SC64_IRQ_USB; irq |= SC64_IRQ_USB;
} }
if (sr & SC64_SR_AUX_IRQ_PENDING) { if ((sr & SC64_SCR_AUX_IRQ_MASK) && (sr & SC64_SCR_AUX_IRQ_PENDING)) {
irq |= SC64_IRQ_AUX; irq |= SC64_IRQ_AUX;
} }
return irq; return irq;

View File

@ -140,6 +140,10 @@ struct _64DDArgs {
#[arg(short, long)] #[arg(short, long)]
rom: Option<PathBuf>, rom: Option<PathBuf>,
/// Attempt to reboot the console (requires specific support in the running game)
#[arg(short, long)]
reboot: bool,
/// Path to the save file (also used by save writeback mechanism) /// Path to the save file (also used by save writeback mechanism)
#[arg(short, long, requires = "rom")] #[arg(short, long, requires = "rom")]
save: Option<PathBuf>, save: Option<PathBuf>,
@ -367,27 +371,13 @@ fn handle_list_command() -> Result<(), sc64::Error> {
} }
fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<(), sc64::Error> { fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<(), sc64::Error> {
const AUX_TOKEN_UPLOAD_START: u32 = 0xFF000001;
const AUX_TOKEN_REBOOT: u32 = 0xFF000002;
let mut sc64 = init_sc64(connection, true)?; let mut sc64 = init_sc64(connection, true)?;
if args.reboot { if args.reboot && !sc64.aux_try_notify(sc64::AuxMessage::IOHalt)? {
match sc64.aux_send_and_receive( println!(
AUX_TOKEN_UPLOAD_START, "{}",
std::time::Duration::from_millis(500), "Warning: no response for [IOHalt] AUX message".bright_yellow()
)? { );
Some(data) => println!(
"{}",
format!("N64 reboot prepare successful (0x{data:08X})")
.bright_green()
.bold()
),
None => println!(
"{}",
"N64 reboot prepare unsuccessful".bright_yellow().bold()
),
}
} }
sc64.reset_state()?; sc64.reset_state()?;
@ -435,8 +425,11 @@ fn handle_upload_command(connection: Connection, args: &UploadArgs) -> Result<()
sc64.calculate_cic_parameters(args.cic_seed)?; sc64.calculate_cic_parameters(args.cic_seed)?;
if args.reboot { if args.reboot && !sc64.aux_try_notify(sc64::AuxMessage::Reboot)? {
sc64.aux_send(AUX_TOKEN_REBOOT)?; println!(
"{}",
"Warning: no response for [Reboot] AUX message".bright_yellow()
);
} }
Ok(()) Ok(())
@ -477,6 +470,13 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
.bright_green() .bright_green()
); );
if args.reboot && !sc64.aux_try_notify(sc64::AuxMessage::IOHalt)? {
println!(
"{}",
"Warning: no response for [IOHalt] AUX message".bright_yellow()
);
}
sc64.reset_state()?; sc64.reset_state()?;
if let Some(rom) = &args.rom { if let Some(rom) = &args.rom {
@ -587,6 +587,13 @@ fn handle_64dd_command(connection: Connection, args: &_64DDArgs) -> Result<(), s
sc64.set_save_writeback(true)?; sc64.set_save_writeback(true)?;
if args.reboot && !sc64.aux_try_notify(sc64::AuxMessage::Reboot)? {
println!(
"{}",
"Warning: no response for [Reboot] AUX message".bright_yellow()
);
}
let exit = setup_exit_flag(); let exit = setup_exit_flag();
while !exit.load(Ordering::Relaxed) { while !exit.load(Ordering::Relaxed) {
if let Some(data_packet) = sc64.receive_data_packet()? { if let Some(data_packet) = sc64.receive_data_packet()? {

View File

@ -13,10 +13,10 @@ pub use self::{
link::list_local_devices, link::list_local_devices,
server::ServerEvent, server::ServerEvent,
types::{ types::{
BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState, DdDriveType, DdMode, AuxMessage, BootMode, ButtonMode, ButtonState, CicSeed, DataPacket, DdDiskState,
DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind, FpgaDebugData, ISViewer, DdDriveType, DdMode, DebugPacket, DiagnosticData, DiskPacket, DiskPacketKind,
MemoryTestPattern, MemoryTestPatternResult, SaveType, SaveWriteback, SpeedTestDirection, FpgaDebugData, ISViewer, MemoryTestPattern, MemoryTestPatternResult, SaveType,
Switch, TvType, SaveWriteback, SpeedTestDirection, Switch, TvType,
}, },
}; };
@ -598,6 +598,15 @@ impl SC64 {
} }
} }
pub fn aux_try_notify(&mut self, message: AuxMessage) -> Result<bool, Error> {
let value: u32 = message.into();
let timeout = std::time::Duration::from_millis(500);
if let Some(response) = self.aux_send_and_receive(value, timeout)? {
return Ok(value == response);
}
Ok(false)
}
pub fn check_device(&mut self) -> Result<(), Error> { pub fn check_device(&mut self) -> Result<(), Error> {
let identifier = self.command_identifier_get().map_err(|e| { let identifier = self.command_identifier_get().map_err(|e| {
Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str()) Error::new(format!("Couldn't get SC64 device identifier: {e}").as_str())

View File

@ -650,6 +650,20 @@ impl TryFrom<AsynchronousPacket> for DataPacket {
} }
} }
pub enum AuxMessage {
IOHalt,
Reboot,
}
impl From<AuxMessage> for u32 {
fn from(value: AuxMessage) -> Self {
match value {
AuxMessage::IOHalt => 0xFF000001,
AuxMessage::Reboot => 0xFF000002,
}
}
}
pub struct DebugPacket { pub struct DebugPacket {
pub datatype: u8, pub datatype: u8,
pub data: Vec<u8>, pub data: Vec<u8>,