From 403ac121710949505332a61482fc26d83c6952a6 Mon Sep 17 00:00:00 2001 From: Zack Buhman Date: Wed, 3 Sep 2025 19:56:31 -0500 Subject: [PATCH] dreamcast2: add maple_keyboard_serial_forward --- dreamcast2/example/example.mk | 6 + dreamcast2/example/maple_device_request.cpp | 16 + dreamcast2/example/maple_get_condition.cpp | 1 - .../example/maple_keyboard_serial_forward.cpp | 437 ++++++++++++++++++ dreamcast2/maple/maple_bus_ft6_scan_code.hpp | 3 +- 5 files changed, 460 insertions(+), 3 deletions(-) create mode 100644 dreamcast2/example/maple_keyboard_serial_forward.cpp diff --git a/dreamcast2/example/example.mk b/dreamcast2/example/example.mk index 88e0347..72fdc76 100644 --- a/dreamcast2/example/example.mk +++ b/dreamcast2/example/example.mk @@ -105,3 +105,9 @@ MAPLE_GET_CONDITION_OBJ = \ example/maple_get_condition.elf: LDSCRIPT = $(LIB)/main.lds example/maple_get_condition.elf: $(START_OBJ) $(MAPLE_GET_CONDITION_OBJ) + +MAPLE_KEYBOARD_SERIAL_FORWARD_OBJ = \ + example/maple_keyboard_serial_forward.o + +example/maple_keyboard_serial_forward.elf: LDSCRIPT = $(LIB)/main.lds +example/maple_keyboard_serial_forward.elf: $(START_OBJ) $(MAPLE_KEYBOARD_SERIAL_FORWARD_OBJ) diff --git a/dreamcast2/example/maple_device_request.cpp b/dreamcast2/example/maple_device_request.cpp index d792fb4..425e00d 100644 --- a/dreamcast2/example/maple_device_request.cpp +++ b/dreamcast2/example/maple_device_request.cpp @@ -24,6 +24,14 @@ */ void maple_dma_start(void * command_buf) { + // write back operand cache blocks for command buffer prior to starting DMA + for (uint32_t i = 0; i < align_32byte(send_size) / 32; i++) { + asm volatile ("ocbwb @%0" + : + : "r" (reinterpret_cast(((uint32_t)command_buf) + 32 * i)) + ); + } + using systembus::maple_if; using systembus::systembus; using namespace maple; @@ -76,6 +84,14 @@ void maple_dma_start(void * command_buf) // start Maple DMA (completes asynchronously) maple_if.MDST = mdst::start_status::start; + + // write back operand cache blocks for command buffer prior to starting DMA + for (uint32_t i = 0; i < align_32byte(recv_size) / 32; i++) { + asm volatile ("ocbi @%0" + : + : "r" (reinterpret_cast(((uint32_t)response_buf) + 32 * i)) + ); + } } void maple_dma_wait_complete() diff --git a/dreamcast2/example/maple_get_condition.cpp b/dreamcast2/example/maple_get_condition.cpp index fbdbea4..5d0df83 100644 --- a/dreamcast2/example/maple_get_condition.cpp +++ b/dreamcast2/example/maple_get_condition.cpp @@ -6,7 +6,6 @@ #include "maple/maple_bus_commands.hpp" #include "maple/maple_bus_bits.hpp" #include "maple/maple_bus_ft0.hpp" -#include "maple/maple_bus_ft6.hpp" #include "sh7091/sh7091.hpp" #include "sh7091/sh7091_bits.hpp" diff --git a/dreamcast2/example/maple_keyboard_serial_forward.cpp b/dreamcast2/example/maple_keyboard_serial_forward.cpp new file mode 100644 index 0000000..351bfa2 --- /dev/null +++ b/dreamcast2/example/maple_keyboard_serial_forward.cpp @@ -0,0 +1,437 @@ +#include "systembus/systembus.hpp" +#include "systembus/systembus_bits.hpp" + +#include "maple/maple_bits.hpp" +#include "maple/maple_host.hpp" +#include "maple/maple_bus_commands.hpp" +#include "maple/maple_bus_bits.hpp" +#include "maple/maple_bus_ft6.hpp" +#include "maple/maple_bus_ft6_scan_code.hpp" + +#include "sh7091/sh7091.hpp" +#include "sh7091/sh7091_bits.hpp" + +#include "holly/holly.hpp" +#include "holly/holly_bits.hpp" + +static inline void character(const char c) +{ + using sh7091::sh7091; + using namespace sh7091; + + // set the transmit trigger to `1 byte`--this changes the behavior of TDFE + sh7091.SCIF.SCFCR2 = scif::scfcr2::ttrg::trigger_on_1_bytes; + + // wait for transmit fifo to become partially empty + while ((sh7091.SCIF.SCFSR2 & scif::scfsr2::tdfe::bit_mask) == 0); + + // unset tdfe bit + sh7091.SCIF.SCFSR2 = (uint16_t)(~scif::scfsr2::tdfe::bit_mask); + + sh7091.SCIF.SCFTDR2 = c; +} + +static void string(const char * s) +{ + while (*s != 0) { + character(*s++); + } +} + +static 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]); + } +} + +constexpr inline uint32_t align_32byte(uint32_t mem) +{ + return (mem + 31) & ~31; +} + +/* + 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, uint32_t send_size, + void * response_buf, uint32_t recv_size) +{ + // write back operand cache blocks for command buffer prior to starting DMA + for (uint32_t i = 0; i < align_32byte(send_size) / 32; i++) { + asm volatile ("ocbwb @%0" + : + : "r" (reinterpret_cast(((uint32_t)command_buf) + 32 * i)) + ); + } + + using systembus::maple_if; + using systembus::systembus; + using namespace maple; + using namespace systembus; + + // 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 receive 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 + maple_if.MDEN = mden::dma_enable::abort; + while ((maple_if.MDST & mdst::start_status::bit_mask) != 0); + + // clear Maple DMA end status + systembus.ISTNRM = istnrm::end_of_dma_maple_dma; + + // 20nsec maple_if. 0xc350 = 1ms + uint32_t one_msec = 0xc350; + // set the Maple bus controller timeout and transfer rate + maple_if.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 receive buffer, but TOP_ADDRESS would need to be lowered + // accordingly. + maple_if.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. + maple_if.MDTSEL = mdtsel::trigger_select::software_initiation; + + // the Maple DMA start address must be 32-byte aligned + maple_if.MDSTAR = mdstar::table_address((uint32_t)command_buf); + + // re-enable Maple DMA + maple_if.MDEN = mden::dma_enable::enable; + + // start Maple DMA (completes asynchronously) + maple_if.MDST = mdst::start_status::start; + + // write back operand cache blocks for command buffer prior to starting DMA + for (uint32_t i = 0; i < align_32byte(recv_size) / 32; i++) { + asm volatile ("ocbi @%0" + : + : "r" (reinterpret_cast(((uint32_t)response_buf) + 32 * i)) + ); + } +} + +int maple_dma_wait_complete() +{ + using systembus::systembus; + using namespace systembus; + + // wait for Maple DMA completion + while ((systembus.ISTNRM & istnrm::end_of_dma_maple_dma) == 0) { + if (systembus.ISTERR) { + string("ISTERR "); + print_base16(systembus.ISTERR, 8); + character('\n'); + if ((systembus.ISTERR >> 11) & 1) { + return -1; + } + } + } + // clear Maple DMA end status + systembus.ISTNRM = istnrm::end_of_dma_maple_dma; + + return 0; +} + +template +void maple_device_request(maple::host_command * host_command, + maple::host_response * host_response, + bool end_flag = false) +{ + using namespace maple; + + constexpr uint32_t data_size = (sizeof (typename C::data_fields)); + + host_command->host_instruction = (end_flag ? host_instruction::end_flag : 0) + | host_instruction::port_select::a + | host_instruction::pattern::normal + | host_instruction::transfer_length(data_size / 4); + + host_command->receive_data_address = ((uint32_t)host_response) & receive_data_address::mask; + + host_command->header.command_code = C::command_code; + + host_command->header.destination_ap = ap::port_select::a + | ap::de::device; + + host_command->header.source_ap = ap::port_select::a + | ap::de::port; + + host_command->header.data_size = data_size / 4; +} + +// the Maple bus protocol is big endian, but the SH4 is running in little endian mode +#define bswap32 __builtin_bswap32 +#define bswap16 __builtin_bswap16 + +void main() +{ + // Maple DMA buffers must be 32-byte aligned + uint8_t send_buf[1024] __attribute__((aligned(32))); + uint8_t recv_buf[1024] __attribute__((aligned(32))); + + struct requests { + maple::host_command device_request; + maple::host_command get_condition; + }; + static_assert((sizeof (requests)) == (sizeof (maple::host_command)) + (sizeof (maple::host_command))); + + using maple__data_transfer = maple::data_transfer; + + struct responses { + maple::host_response device_status; + maple::host_response data_transfer; + }; + + auto requests = (struct requests *)send_buf; + auto responses = (struct responses *)recv_buf; + + // fill send_buf with a "device request" command + // recv_buf is reply destination address + maple_device_request(&requests->device_request, &responses->device_status); + maple_device_request(&requests->get_condition, &responses->data_transfer, true); + + requests->get_condition.data.function_type = bswap32(maple::function_type::keyboard); + + systembus::systembus.ISTERR = 0xffffffff; + + maple_dma_start(send_buf, (sizeof (struct requests)), + recv_buf, (sizeof (struct responses))); + + string("wait\n"); + int res = maple_dma_wait_complete(); + if (res != 0) { + string(" "); + return; + } + + if (responses->device_status.header.command_code != maple::device_status::command_code) { + string("maple port A: invalid response or disconnected\n"); + string(" "); + return; + } + + ////////////////////////////////////////////////////////////////////////////// + // data transfer + ////////////////////////////////////////////////////////////////////////////// + + string("responses->device_status:\n"); + string(" header:\n"); + string(" command_code : "); + print_base16(responses->device_status.header.command_code, 2); character('\n'); + string(" destination_ap : "); + print_base16(responses->device_status.header.destination_ap, 2); character('\n'); + string(" source_ap : "); + print_base16(responses->device_status.header.source_ap, 2); character('\n'); + string(" data_size : "); + print_base16(responses->device_status.header.data_size, 2); character('\n'); + + auto& device_status = responses->device_status.data; + + string(" data:\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'); + + ////////////////////////////////////////////////////////////////////////////// + // data transfer + ////////////////////////////////////////////////////////////////////////////// + + if (responses->data_transfer.header.command_code != maple__data_transfer::command_code) { + string("maple port A: invalid response or disconnected\n"); + print_base16(responses->data_transfer.header.command_code, 8); + character('\n'); + string(" "); + return; + } + + string("responses->data_transfer:\n"); + string(" header:\n"); + string(" command_code : "); + print_base16(responses->data_transfer.header.command_code, 2); character('\n'); + string(" destination_ap : "); + print_base16(responses->data_transfer.header.destination_ap, 2); character('\n'); + string(" source_ap : "); + print_base16(responses->data_transfer.header.source_ap, 2); character('\n'); + string(" data_size : "); + print_base16(responses->data_transfer.header.data_size, 2); character('\n'); + + auto& data_transfer = responses->data_transfer.data; + + string(" data:\n"); + string(" function_type: "); + print_base16(bswap32(data_transfer.function_type), 8); character('\n'); + string(" data:\n"); + string(" modifier_key: "); + print_base16(data_transfer.data.modifier_key, 2); character('\n'); + string(" led_state: "); + print_base16(data_transfer.data.led_state, 2); character('\n'); + string(" scan_code_array[0]: "); + print_base16(data_transfer.data.scan_code_array[0], 2); character('\n'); + string(" scan_code_array[1]: "); + print_base16(data_transfer.data.scan_code_array[1], 2); character('\n'); + string(" scan_code_array[2]: "); + print_base16(data_transfer.data.scan_code_array[2], 2); character('\n'); + string(" scan_code_array[3]: "); + print_base16(data_transfer.data.scan_code_array[3], 2); character('\n'); + string(" scan_code_array[4]: "); + print_base16(data_transfer.data.scan_code_array[4], 2); character('\n'); + string(" scan_code_array[5]: "); + print_base16(data_transfer.data.scan_code_array[5], 2); character('\n'); + + uint8_t scan_code_array[6]; + + for (int i = 0; i < 6; i++) { + scan_code_array[i] = data_transfer.data.scan_code_array[i]; + } + + { + using namespace systembus; + using namespace maple; + + maple_if.MDEN = mden::dma_enable::abort; + while ((maple_if.MDST & mdst::start_status::bit_mask) != 0); + + // clear Maple DMA end status + systembus::systembus.ISTNRM = istnrm::end_of_dma_maple_dma; + + maple_if.MDTSEL = mdtsel::trigger_select::v_blank_initiation; + + maple_if.MDAPRO = mdapro::security_code + | mdapro::top_address(0x40) + | mdapro::bottom_address(0x7f); + + maple_if.MDSTAR = mdstar::table_address((uint32_t)(&requests->get_condition)); + + maple_if.MDEN = mden::dma_enable::enable; + } + + while (1) { + for (uint32_t i = 0; i < align_32byte((sizeof (struct responses))) / 32; i++) { + asm volatile ("ocbi @%0" + : + : "r" (reinterpret_cast(((uint32_t)recv_buf) + 32 * i)) + ); + } + + while ((systembus::systembus.ISTNRM & systembus::istnrm::v_blank_in) == 0); + systembus::systembus.ISTNRM = systembus::istnrm::v_blank_in; + + asm volatile ("" ::: "memory"); + + /* + if (responses->device_status.header.command_code != maple::device_status::command_code) { + //string("device_status: invalid response or disconnected\n"); + //print_base16(responses->data_transfer.header.command_code, 2); + //character('\n'); + //string(" "); + continue; + } + */ + + if (responses->data_transfer.header.command_code != maple__data_transfer::command_code) { + //string("data_transfer: invalid response or disconnected\n"); + //print_base16(responses->data_transfer.header.command_code, 2); + //character('\n'); + //string(" "); + continue; + } + + bool rollover = data_transfer.data.scan_code_array[5] == maple::ft6::scan_code::rollover_error; + if (rollover) + string("rollover error\n"); + for (int i = 5; i >= 0; i--) { + if (data_transfer.data.scan_code_array[i] == maple::ft6::scan_code::no_operation) + continue; + + bool make = true; + for (int j = 0; j < 6; j++) { + if (scan_code_array[j] == data_transfer.data.scan_code_array[i]) { + make = false; + break; + } + } + if (!make) + continue; + + //print_base16(data_transfer.data.scan_code_array[i], 2); character('\n'); + //for (int j = 0; j < 6; j++) { + //print_base16(data_transfer.data.scan_code_array[j], 2); character(' '); + //} + //character('\n'); + bool lshift = (maple::ft6::data_transfer::modifier_key::left_shift() & data_transfer.data.modifier_key) != 0; + bool rshift = (maple::ft6::data_transfer::modifier_key::right_shift() & data_transfer.data.modifier_key) != 0; + bool shift = lshift || rshift; + character(maple::ft6::scan_code::code_point[data_transfer.data.scan_code_array[i]][shift]); + } + + for (int i = 0; i < 6; i++) { + scan_code_array[i] = data_transfer.data.scan_code_array[i]; + } + } + + string(" "); +} diff --git a/dreamcast2/maple/maple_bus_ft6_scan_code.hpp b/dreamcast2/maple/maple_bus_ft6_scan_code.hpp index 2105976..63054b3 100644 --- a/dreamcast2/maple/maple_bus_ft6_scan_code.hpp +++ b/dreamcast2/maple/maple_bus_ft6_scan_code.hpp @@ -133,7 +133,7 @@ namespace maple::ft6::scan_code { [scan_code::esc] = { 0, 0 }, [scan_code::backspace] = { 0, 0 }, [scan_code::tab] = { 0, 0 }, - [scan_code::spacebar] = { 0, 0 }, + [scan_code::spacebar] = { ' ', ' ' }, [scan_code::minus_underscore] = { '-', '_' }, [scan_code::equal_plus] = { '=', '+' }, [scan_code::bracketleft_braceleft] = { '[', '{' }, @@ -148,4 +148,3 @@ namespace maple::ft6::scan_code { [scan_code::slash_question] = { '/', '?' }, }; } -