diff --git a/maple.c b/maple.c new file mode 100644 index 0000000..28639da --- /dev/null +++ b/maple.c @@ -0,0 +1,403 @@ +#include + +// +// SH4 SCIF definitions +// + +// SH4 on-chip peripheral module control +volatile uint8_t * SCFTDR2 = (volatile uint8_t *)(0xffe80000 + 0x0c); +volatile uint16_t * SCFSR2 = (volatile uint16_t *)(0xffe80000 + 0x10); +volatile uint16_t * SCFCR2 = (volatile uint16_t *)(0xffe80000 + 0x18); + +#define SCFSR2__TDFE (1 << 5) +#define SCFSR2__TEND (1 << 6) + +#define SCFCR2__TTRG(n) (((n) & 0b11) << 4) + +static inline void character(const char c) +{ + // wait for transmit fifo to become partially empty + while ((*SCFSR2 & SCFSR2__TDFE) == 0); + + // unset TDFE bit + *SCFSR2 &= ~SCFSR2__TDFE; + + *SCFTDR2 = c; +} + +static inline void string(const char * s) +{ + while (*s != 0) { + character(*s++); + } +} + +void print_base16(uint32_t n, int len) +{ + char buf[len]; + char * bufi = &buf[len - 1]; + + while (bufi >= buf) { + uint32_t nib = n & 0xf; + n = n >> 4; + if (nib > 9) { + nib += (97 - 10); + } else { + nib += (48 - 0); + } + + *bufi = nib; + bufi -= 1; + } + + for (int i = 0; i < len; i++) { + character(buf[i]); + } +} + + +// +// Maple protocol definitions +// + +/* + The Maple bus controller is a functional unit inside Holly. The only way to + interact with it is via "Maple DMA"--a special DMA unit only used for + interactions with the Maple controller. + + The "Maple DMA" unit is really more of a "FIFO", similar to the TA, except the + Maple controller that happens to have a hard dependency on SH4 DMA (unlike the + TA which the a more relaxed restriction "must use 32-byte transfers"). + + The structure of the transmit buffer given to the Maple DMA unit is always: + + [ host instruction ] + [ recieve data address ] + [ protocol data ] + [ command code ] + [ destination ap ] + [ source ap ] + [ data size ] + [ (variable-length data) ] + [ host instruction ] + [ recieve data address ] + [ protocol data ] + ... + + Each "host instruction" identifies the Maple port/destination that the + following "Maple protocol data" is sent to. There can be multiple host + instructions in a single Maple DMA buffer. + + The "recieve data address" is the System Memory address where you want the + reply to your Maple command to be stored. + + See DCDBSysArc990907E.pdf page 276. + + See also MAPLE82E.pdf page 20 -- this shows the Maple bus from the + perspective of (e.g) a Dreamcast Controller. + */ + +typedef struct maple_protocol_header { + uint8_t command_code; + uint8_t destination_ap; + uint8_t source_ap; + uint8_t data_size; +} maple_protocol_header; + +typedef struct maple_host_command { + uint32_t host_instruction; // interpreted by the Maple DMA controller + uint32_t receive_data_address; // interpreted by the Maple DMA controller + + maple_protocol_header protocol_header; + + // variable-length "parameter" data follows +} maple_host_command; + +// Maple Host Instruction : DCDBSysArc990907E.pdf page 269 +#define MAPLE__HOST_INSTRUCTION__END_FLAG (1 << 31) +#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__A (0 << 16) +#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__B (1 << 16) +#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__C (2 << 16) +#define MAPLE__HOST_INSTRUCTION__PORT_SELECT__D (3 << 16) +#define MAPLE__HOST_INSTRUCTION__PATTERN__NORMAL (0b000 << 8) +#define MAPLE__HOST_INSTRUCTION__TRANSFER_LENGTH(n) (((n) & 0xff) << 0) + +// (supported) command codes are defined per-peripheral; there is also general +// information about command codes in MAPLE82E.pdf page 72. + +// "AP" is defined in MAPLE82E.pdf page 21 + +#define MAPLE__AP__PORT_SELECT__A (0b00 << 6) +#define MAPLE__AP__PORT_SELECT__B (0b01 << 6) +#define MAPLE__AP__PORT_SELECT__C (0b10 << 6) +#define MAPLE__AP__PORT_SELECT__D (0b11 << 6) + +// "device" roughly means "the thing directly connected to the Dreamcast" +// +// "expansion device" roughly means "the thing with some indirect connection to +// the Dreamcast (e.g: a VMU)" +// +// "port" is used to indicate that the AP is the Dreamcast itself (via that +// port) +#define MAPLE__AP__DE__DEVICE (1 << 5) +#define MAPLE__AP__DE__EXPANSION_DEVICE (0 << 5) +#define MAPLE__AP__DE__PORT (0 << 5) + +// Ft0_090e.pdf describes the commands supported by Dreamcast Controllers +// specifically. +// +// The simplest command supported by a Dreamcast Controller is the "Device +// Request" command. The reply to a "Device Request" command is "Device Status". +#define MAPLE__COMMAND_CODE__DEVICE_REQUEST (0x01) +#define MAPLE__COMMAND_CODE__DEVICE_STATUS (0x05) + +// A Maple DMA response is simply the Maple protocol data sent by the target/AP in +// response to the command + +typedef struct maple_host_reply { + maple_protocol_header protocol_header; + + // variable-length "parameter" data follows +} maple_host_reply; + +// the Device Request command has no fields + +// the Device Status reply has these fields: + +typedef struct maple_device_id { + uint32_t ft; + uint32_t fd[3]; +} maple_device_id; + +typedef struct maple_device_status { + maple_device_id device_id; + uint8_t destination_code; + uint8_t connection_direction; + uint8_t product_name[30]; + uint8_t license[60]; + uint16_t low_consumption_standby_current; + uint16_t maximum_current_consumption; +} maple_device_status; + +/* + maple_device_status is concatenated after maple_host_reply, as in: + + struct { + maple_host_reply host_reply; + maple_device_status device_status; + }; + + This ^ is the structure that will be stored at `recieve_data_address`. +*/ + +// +// Maple interface definitions +// + +// system bus interface +volatile uint32_t * ISTNRM = (volatile uint32_t *)(0xa05f6800 + 0x100); + +#define ISTNRM__END_OF_DMA_MAPLE_DMA (1 << 12) + +// Maple interface +volatile uint32_t * MDSTAR = (volatile uint32_t *)(0xa05f6c00 + 0x04); +volatile uint32_t * MDTSEL = (volatile uint32_t *)(0xa05f6c00 + 0x10); +volatile uint32_t * MDEN = (volatile uint32_t *)(0xa05f6c00 + 0x14); +volatile uint32_t * MDST = (volatile uint32_t *)(0xa05f6c00 + 0x18); +volatile uint32_t * MSYS = (volatile uint32_t *)(0xa05f6c00 + 0x80); +volatile uint32_t * MDAPRO = (volatile uint32_t *)(0xa05f6c00 + 0x8c); + +#define MDEN__DMA_ENABLE__ABORT (0 << 0) +#define MDEN__DMA_ENABLE__ENABLE (1 << 0) +#define MDST__START_STATUS (1 << 0) + +#define MSYS__TIME_OUT_COUNTER(n) (((n) & 0xffff) << 16) +#define MSYS__SENDING_RATE___2M (0 << 8) + +#define MDAPRO__SECURITY_CODE (0x6155 << 16) +#define MDAPRO__TOP_ADDRESS(n) (((n) & 0x7f) << 8) +#define MDAPRO__BOTTOM_ADDRESS(n) (((n) & 0x7f) << 0) + +#define MDTSEL__TRIGGER_SELECT__SOFTWARE_INITIATION (0 << 0) + +#define MDSTAR__TABLE_ADDRESS(n) (((n) & 0xfffffe0) << 0) + +/* + See DCDBSysArc990907E.pdf page 274. + + Maple DMA uses SH4 DMA channel 0 in "DDT" mode. This means that Holly's Maple + DMA functional unit is externally controlling the SH4's DMA unit. + + The Dreamcast boot rom leaves channel 0 already configured for Maple DMA, + though if you felt rebellious and wished to used channel 0 for something else, + you would need to reconfigure channel 0 for Maple/DDT afterwards. + + Note that this `maple_dma_start` function does not configure SH4 DMA channel + 0, and presumes it is already in the correct state. + */ +void maple_dma_start(void * command_buf) +{ + // if SH4 cache were enabled, it would be necessary to use the `ocbwb` sh4 + // instruction to write back the cache to system memory, prior to continuing. + + // if SH4 cache were enabled, it would be necessary to use the `ocbi` sh4 + // instruction to invalidate any possibly-cached areas of the recieve buffer, + // as these are imminently going to be rewritten by the DMA unit independently + // of cache access. + + // disable Maple DMA and abort any possibly-in-progress transfers + *MDEN = MDEN__DMA_ENABLE__ABORT; + while ((*MDST & MDST__START_STATUS) != 0); + + // clear Maple DMA end status + *ISTNRM = ISTNRM__END_OF_DMA_MAPLE_DMA; + + // 20nsec * 0xc350 = 1ms + uint32_t one_msec = 0xc350; + // set the Maple bus controller timeout and transfer rate + *MSYS = MSYS__TIME_OUT_COUNTER(one_msec) + | MSYS__SENDING_RATE___2M; + + // MDAPRO controls which (system memory) addresses are considered valid for + // Maple DMA. An attempt to use an address outside of this range will cause an + // error interrupt in from the Maple DMA unit. + // + // 0x40 through 0x7F allows for transfers from 0x0c000000 to 0x0fffffff + // System Memory is 0x0c000000 through 0x0cffffff (16MB) + // + // It is probably possible to do strange things such as use texture memory as + // a Maple DMA recieve buffer, but TOP_ADDRESS would need to be lowered + // accordingly. + *MDAPRO = MDAPRO__SECURITY_CODE + | MDAPRO__TOP_ADDRESS(0x40) + | MDAPRO__BOTTOM_ADDRESS(0x7f); + + // SOFTWARE_INITIATION allows a Maple DMA transfer to be initiated by a write + // to the MDST register. + *MDTSEL = MDTSEL__TRIGGER_SELECT__SOFTWARE_INITIATION; + + // the Maple DMA start address must be 32-byte aligned + *MDSTAR = MDSTAR__TABLE_ADDRESS((uint32_t)command_buf); + + // re-enable Maple DMA + *MDEN = MDEN__DMA_ENABLE__ENABLE; + + // start Maple DMA (completes asynchronously) + *MDST = MDST__START_STATUS; +} + +void maple_dma_wait_complete() +{ + // wait for Maple DMA completion + while ((*ISTNRM & ISTNRM__END_OF_DMA_MAPLE_DMA) == 0); + // clear Maple DMA end status + *ISTNRM = ISTNRM__END_OF_DMA_MAPLE_DMA; +} + +// +// Maple command buffer construction +// + +void maple_device_request(uint8_t * send_buf, uint8_t * recv_buf) +{ + maple_host_command * host_command = (maple_host_command *)send_buf; + + host_command->host_instruction = MAPLE__HOST_INSTRUCTION__END_FLAG + | MAPLE__HOST_INSTRUCTION__PORT_SELECT__A + | MAPLE__HOST_INSTRUCTION__PATTERN__NORMAL + | MAPLE__HOST_INSTRUCTION__TRANSFER_LENGTH(0); + + host_command->receive_data_address = (uint32_t)recv_buf; + + host_command->protocol_header.command_code = MAPLE__COMMAND_CODE__DEVICE_REQUEST; + + host_command->protocol_header.destination_ap = MAPLE__AP__PORT_SELECT__A + | MAPLE__AP__DE__DEVICE; + + host_command->protocol_header.source_ap = MAPLE__AP__PORT_SELECT__A + | MAPLE__AP__DE__PORT; + + host_command->protocol_header.data_size = 0 / 4; +} + +void main() +{ + // set the transmit trigger to `1 byte`--this changes the behavior of TDFE + *SCFCR2 = SCFCR2__TTRG(0b11); + + // Maple DMA buffers must be 32-byte aligned + uint8_t send_buf[1024] __attribute__((aligned(32))); + uint8_t recv_buf[1024] __attribute__((aligned(32))); + + // fill send_buf with a "device request" command + // recv_buf is reply destination address + maple_device_request(send_buf, recv_buf); + + maple_dma_start(send_buf); + + maple_dma_wait_complete(); + + // decode the reply in recv_buf + + maple_host_reply * host_reply = (maple_host_reply *)recv_buf; + + if (host_reply->protocol_header.command_code != MAPLE__COMMAND_CODE__DEVICE_STATUS) { + string("maple port A: invalid response or disconnected\n"); + return; + } + + string("host_reply:\n"); + string(" protocol_header:\n"); + string(" command_code : "); + print_base16(host_reply->protocol_header.command_code, 2); character('\n'); + string(" destination_ap : "); + print_base16(host_reply->protocol_header.destination_ap, 2); character('\n'); + string(" source_ap : "); + print_base16(host_reply->protocol_header.source_ap, 2); character('\n'); + string(" data_size : "); + print_base16(host_reply->protocol_header.data_size, 2); character('\n'); + + maple_device_status * device_status = (maple_device_status *)(recv_buf + (sizeof (maple_host_reply))); + + // the Maple bus protocol is big endian, but the SH4 is running in little endian mode + #define bswap32 __builtin_bswap32 + #define bswap16 __builtin_bswap16 + + string(" device_status:\n"); + string(" device_id:\n"); + string(" ft : "); + print_base16(bswap32(device_status->device_id.ft), 8); character('\n'); + string(" fd[0]: "); + print_base16(bswap32(device_status->device_id.fd[0]), 8); character('\n'); + string(" fd[1]: "); + print_base16(bswap32(device_status->device_id.fd[1]), 8); character('\n'); + string(" fd[2]: "); + print_base16(bswap32(device_status->device_id.fd[2]), 8); character('\n'); + string(" destination_code: "); + print_base16(bswap32(device_status->destination_code), 2); character('\n'); + string(" connection_direction: "); + print_base16(bswap32(device_status->connection_direction), 2); character('\n'); + string(" product_name: \""); + for (int i = 0; i < 30; i++) + character(device_status->product_name[i]); + string("\"\n"); + string(" license: \""); + for (int i = 0; i < 60; i++) + character(device_status->license[i]); + string("\"\n"); + string(" low_consumption_standby_current: "); + print_base16(bswap16(device_status->low_consumption_standby_current), 4); character('\n'); + string(" maximum_current_consumption: "); + print_base16(bswap16(device_status->maximum_current_consumption), 4); character('\n'); + + // hack: "flush" the above characters before the serial loader resets the + // serial interface + // + // Other methods to detect "transmit fifo is empty" appear to be + // non-functional (including polling SCFDR2 or SCFSR2) + // + // It would also be appropriate to use an SH7091 timer here, to delay a + // certain number of milliseconds, though that would require defining more + // registers. + string(" "); +} diff --git a/serial.c b/serial.c index 7db1781..0c3a725 100644 --- a/serial.c +++ b/serial.c @@ -1,8 +1,18 @@ #include -volatile uint8_t * SCFTDR2 = (volatile uint8_t *)0xffe8000c; -volatile uint16_t * SCFSR2 = (volatile uint16_t *)0xffe80010; -volatile uint16_t * SCFCR2 = (volatile uint16_t *)0xffe80018; +/* + This example does not configure the SCIF, and presumes it is already + configured for UART transmission. + + This is *not* the default SCIF state as initialized by the Dreamcast boot + rom. However, the serial loader does not attempt to un-initialize the SCIF + prior to jumping to system memory. + */ + +// SH4 on-chip peripheral module control +volatile uint8_t * SCFTDR2 = (volatile uint8_t *)(0xffe80000 + 0x0c); +volatile uint16_t * SCFSR2 = (volatile uint16_t *)(0xffe80000 + 0x10); +volatile uint16_t * SCFCR2 = (volatile uint16_t *)(0xffe80000 + 0x18); #define SCFSR2__TDFE (1 << 5) #define SCFSR2__TEND (1 << 6)