diff --git a/README.md b/README.md index 7c8710a..252bfbb 100644 --- a/README.md +++ b/README.md @@ -354,7 +354,7 @@ Below defines a location word which is used to address blocks of memory in some ### CRC -CRC byte transmits last, just before the end sequence is transmitted. It is the value after starting with 0 and applying XOR to each other byte in the packet. +CRC byte transmits last, just before the end sequence is transmitted. It is the value after starting with 0 and applying XOR against each byte in the packet. --- @@ -380,7 +380,7 @@ All button bits are 0 when pressed and 1 when released. ## Screen -The "block write" command (0x0C) with screen function code and 48 data words is used to write monochrome images to the screen. A screen is 48 bits wide and 32 bits tall. For each bit in the 48 data words, a value of 1 means the pixel is on (black) and 0 means the pixel is off (white). Data is written from left to right and top to bottom. The most significant bit of the first word sets the pixel on the top, left of the screen. The two most significant bytes write to the 33rd through 48th bit of the first row. The next two bytes write to the 1st through 16th bits of the second row. This is repeated for the right of the 48 words like pictured below. +The "block write" command (0x0C) with screen function code and 48 data words is used to write monochrome images to the screen. A screen is 48 bits wide and 32 bits tall. For each bit in the 48 data words, a value of 1 means the pixel is on (black) and 0 means the pixel is off (white). Data is written from left to right and top to bottom. The most significant bit of the first word sets the pixel on the top, left of the screen. The two most significant bytes write to the 33rd through 48th bit of the first row. The next two bytes write to the 1st through 16th bits of the second row. This is repeated for the rest of the 48 words like pictured below.

Screen Words @@ -432,7 +432,7 @@ Specifically, the text `Fm:4 - 30Hz`. This correlates to `(value + 1) / 2` and m - `0xX0`: single stable vibration (i.e. no inclination) at power X - `0xX8`: positive inclination (ramp up) from power X up to max - `0x8X`: negative inclination (ramp down) from power X down to min -- A values of `0x00`, `0x08`, or `0x80` immediately stops the currently executing vibration sequence +- A value of `0x00`, `0x08`, or `0x80` immediately stops the currently executing vibration sequence There is a very noticeable change from one vibration power to the next when inclination is used and a long cycle period is selected. @@ -468,8 +468,16 @@ Bit 0 may be set to 1 to augment duration, but the meaning is not completely und **Maple Bus Resources** +https://archive.org/details/MaplePatent/page/n7/mode/1up + http://mc.pp.se/dc/maplebus.html and http://mc.pp.se/dc/controller.html https://tech-en.netlify.app/articles/en540236/index.html https://www.raphnet.net/programmation/dreamcast_usb/index_en.php + +https://web.archive.org/web/20100425041817/http://www.maushammer.com/vmu.html + +https://hackaday.io/project/170365-blueretro/log/180790-evolution-of-segas-io-interface-from-sg-1000-to-saturn + +https://segaretro.org/History_of_the_Sega_Dreamcast/Development diff --git a/inc/configuration.h b/inc/configuration.h index 3d8fbb3..88269ad 100644 --- a/inc/configuration.h +++ b/inc/configuration.h @@ -5,6 +5,9 @@ // Warning: enabling debug messages drastically degrades communication performance #define SHOW_DEBUG_MESSAGES false +// true to enable USB CDC (serial) interface to directly control the maple bus +#define USB_CDC_ENABLED true + // Adjust the CPU clock frequency here (133 MHz is maximum documented stable frequency) #define CPU_FREQ_KHZ 133000 diff --git a/inc/hal/System/LockGuard.hpp b/inc/hal/System/LockGuard.hpp index 2282608..7522298 100644 --- a/inc/hal/System/LockGuard.hpp +++ b/inc/hal/System/LockGuard.hpp @@ -15,21 +15,29 @@ class LockGuard LockGuard() = delete; //! Initializes data and locks mutex as long as it wouldn't cause a deadlock - inline LockGuard(MutexInterface& mutex) : + inline LockGuard(MutexInterface& mutex, bool allowDeadlock = false) : mMutex(mutex), mIsLocked(false) { - int8_t lockValue = mMutex.tryLock(); - if (lockValue == 0) + if (allowDeadlock) { mMutex.lock(); mIsLocked = true; } - else if (lockValue > 0) + else { - mIsLocked = true; + int8_t lockValue = mMutex.tryLock(); + if (lockValue == 0) + { + mMutex.lock(); + mIsLocked = true; + } + else if (lockValue > 0) + { + mIsLocked = true; + } + // Else: not locked, would cause deadlock - due to simultaneous IRQ access on same core } - // Else: not locked, would cause deadlock } //! Unlocks mutex if it was previously locked diff --git a/inc/hal/Usb/CommandParser.hpp b/inc/hal/Usb/CommandParser.hpp new file mode 100644 index 0000000..9124b73 --- /dev/null +++ b/inc/hal/Usb/CommandParser.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include + +#include "hal/System/MutexInterface.hpp" + +// Command structure: [whitespace][command]<\n> + +//! Command parser for processing commands from a TTY stream +class CommandParser +{ +public: + virtual ~CommandParser() {} + + //! @returns the string of command characters this parser handles + virtual const char* getCommandChars() = 0; + + //! Called when newline reached; submit command and reset + virtual void submit(const char* chars, uint32_t len) = 0; + + //! Prints help message for this command + virtual void printHelp() = 0; +}; diff --git a/inc/hal/Usb/TtyParser.hpp b/inc/hal/Usb/TtyParser.hpp new file mode 100644 index 0000000..455d17e --- /dev/null +++ b/inc/hal/Usb/TtyParser.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include "hal/Usb/CommandParser.hpp" +#include "hal/System/MutexInterface.hpp" + +//! Command parser for processing commands from a TTY stream +class TtyParser +{ +public: + //! Virtual destructor + virtual ~TtyParser() {} + //! Adds a command parser to my list of parsers - must be done before any other function called + virtual void addCommandParser(std::shared_ptr parser) = 0; + //! Called from the process handling maple bus execution + virtual void process() = 0; +}; + +TtyParser* usb_cdc_create_parser(MutexInterface* m, char helpChar); diff --git a/inc/hal/Usb/usb_interface.hpp b/inc/hal/Usb/usb_interface.hpp index 857ee43..3bf3c6c 100644 --- a/inc/hal/Usb/usb_interface.hpp +++ b/inc/hal/Usb/usb_interface.hpp @@ -4,11 +4,14 @@ #include "UsbFileSystem.hpp" #include "DreamcastControllerObserver.hpp" #include "hal/System/MutexInterface.hpp" +#include //! @returns array of the USB controller observers DreamcastControllerObserver** get_usb_controller_observers(); //! USB initialization -void usb_init(MutexInterface* mscMutex, MutexInterface* cdcMutex); +void usb_init( + MutexInterface* mscMutex, + MutexInterface* cdcStdioMutex); //! USB task that needs to be called constantly by main() void usb_task(); //! @returns number of USB controllers diff --git a/src/coreLib/CMakeLists.txt b/src/coreLib/CMakeLists.txt index ab08877..3d5ce98 100644 --- a/src/coreLib/CMakeLists.txt +++ b/src/coreLib/CMakeLists.txt @@ -2,7 +2,9 @@ cmake_minimum_required(VERSION 3.12) set(CMAKE_VERBOSE_MAKEFILE ON) -file(GLOB SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.c*" "${CMAKE_CURRENT_SOURCE_DIR}/peripherals/*.c*") +file(GLOB SRC "${CMAKE_CURRENT_SOURCE_DIR}/*.c*" + "${CMAKE_CURRENT_SOURCE_DIR}/peripherals/*.c*" + "${CMAKE_CURRENT_SOURCE_DIR}/parsers/*.c*") add_library(coreLib STATIC ${SRC}) @@ -19,4 +21,5 @@ target_include_directories(coreLib "$" "$" "${PROJECT_SOURCE_DIR}/inc" - "${CMAKE_CURRENT_SOURCE_DIR}/peripherals") + "${CMAKE_CURRENT_SOURCE_DIR}/peripherals" + "${CMAKE_CURRENT_SOURCE_DIR}/parsers") diff --git a/src/coreLib/DreamcastMainNode.cpp b/src/coreLib/DreamcastMainNode.cpp index 92ace10..b2f67a0 100644 --- a/src/coreLib/DreamcastMainNode.cpp +++ b/src/coreLib/DreamcastMainNode.cpp @@ -4,17 +4,13 @@ #include "DreamcastController.hpp" #include "EndpointTxScheduler.hpp" -const uint8_t DreamcastMainNode::MAIN_TRANSMISSION_PRIORITY = 0; -const uint8_t DreamcastMainNode::SUB_TRANSMISSION_PRIORITY = 1; -const uint8_t DreamcastMainNode::MAX_PRIORITY = 1; - DreamcastMainNode::DreamcastMainNode(MapleBusInterface& bus, PlayerData playerData, std::shared_ptr prioritizedTxScheduler) : DreamcastNode(DreamcastPeripheral::MAIN_PERIPHERAL_ADDR_MASK, std::make_shared( prioritizedTxScheduler, - MAIN_TRANSMISSION_PRIORITY, + PrioritizedTxScheduler::MAIN_TRANSMISSION_PRIORITY, DreamcastPeripheral::getRecipientAddress( playerData.playerIndex, DreamcastPeripheral::MAIN_PERIPHERAL_ADDR_MASK) ), @@ -32,7 +28,7 @@ DreamcastMainNode::DreamcastMainNode(MapleBusInterface& bus, addr, std::make_shared( prioritizedTxScheduler, - SUB_TRANSMISSION_PRIORITY, + PrioritizedTxScheduler::SUB_TRANSMISSION_PRIORITY, DreamcastPeripheral::getRecipientAddress(playerData.playerIndex, addr)), mPlayerData)); } diff --git a/src/coreLib/DreamcastMainNode.hpp b/src/coreLib/DreamcastMainNode.hpp index d6c4c61..16eb0b2 100644 --- a/src/coreLib/DreamcastMainNode.hpp +++ b/src/coreLib/DreamcastMainNode.hpp @@ -65,12 +65,6 @@ class DreamcastMainNode : public DreamcastNode public: //! Number of microseconds in between each info request when no peripheral is detected static const uint32_t US_PER_CHECK = 16000; - //! Main node has highest priority - static const uint8_t MAIN_TRANSMISSION_PRIORITY; - //! Sub nodes have lower priority - static const uint8_t SUB_TRANSMISSION_PRIORITY; - //! Maximum allowed priority - static const uint8_t MAX_PRIORITY; protected: //! The sub nodes under this node diff --git a/src/coreLib/PrioritizedTxScheduler.cpp b/src/coreLib/PrioritizedTxScheduler.cpp index f671eab..27db2c6 100644 --- a/src/coreLib/PrioritizedTxScheduler.cpp +++ b/src/coreLib/PrioritizedTxScheduler.cpp @@ -2,20 +2,23 @@ #include "configuration.h" #include "utils.h" +#include + // STL #include -PrioritizedTxScheduler::PrioritizedTxScheduler(uint8_t maxPriority) : +PrioritizedTxScheduler::PrioritizedTxScheduler() : mNextId(1), mSchedule() { - mSchedule.resize(maxPriority + 1); + mSchedule.resize(PRIORITY_COUNT); } PrioritizedTxScheduler::~PrioritizedTxScheduler() {} uint32_t PrioritizedTxScheduler::add(std::shared_ptr tx) { + assert(tx->priority < mSchedule.size()); std::list>& schedule = mSchedule[tx->priority]; // Keep iterating until correct position is found std::list>::const_iterator iter = schedule.cbegin(); @@ -46,6 +49,9 @@ uint32_t PrioritizedTxScheduler::add(uint8_t priority, uint32_t pktDurationUs = INT_DIVIDE_CEILING(pktDurationNs, 1000); + // This will happen if minimal communication is made constantly for 20 days + assert(mNextId != INVALID_TX_ID); + std::shared_ptr tx = std::make_shared(mNextId++, priority, diff --git a/src/coreLib/PrioritizedTxScheduler.hpp b/src/coreLib/PrioritizedTxScheduler.hpp index f1ccd6e..024bef9 100644 --- a/src/coreLib/PrioritizedTxScheduler.hpp +++ b/src/coreLib/PrioritizedTxScheduler.hpp @@ -9,9 +9,23 @@ class PrioritizedTxScheduler { +public: + //! Enumerates available priorities to be used with add() + enum Priority : uint8_t + { + //! Priority for external entity taking control of the bus (max) + EXTERNAL_TRANSMISSION_PRIORITY = 0, + //! Priority for main peripheral + MAIN_TRANSMISSION_PRIORITY, + //! Priority for sub peripheral (min) + SUB_TRANSMISSION_PRIORITY, + //! Any selected priority must be less than PRIORITY_COUNT + PRIORITY_COUNT + }; + public: //! Default constructor - PrioritizedTxScheduler(uint8_t maxPriority); + PrioritizedTxScheduler(); //! Virtual destructor virtual ~PrioritizedTxScheduler(); @@ -78,6 +92,8 @@ class PrioritizedTxScheduler public: //! Use this for txTime if the packet needs to be sent ASAP static const uint64_t TX_TIME_ASAP = 0; + //! Transmission ID to use in order to flag no ID + static const uint32_t INVALID_TX_ID = 0; protected: //! The next transmission ID to set diff --git a/src/coreLib/parsers/MaplePassthroughCommandParser.cpp b/src/coreLib/parsers/MaplePassthroughCommandParser.cpp new file mode 100644 index 0000000..345c119 --- /dev/null +++ b/src/coreLib/parsers/MaplePassthroughCommandParser.cpp @@ -0,0 +1,155 @@ +#include "MaplePassthroughCommandParser.hpp" +#include "hal/MapleBus/MaplePacket.hpp" + +#include + +// Simple definition of a transmitter which just echos status and received data +class EchoTransmitter : public Transmitter +{ +public: + virtual void txStarted(std::shared_ptr tx) final + {} + + virtual void txFailed(bool writeFailed, + bool readFailed, + std::shared_ptr tx) final + { + if (writeFailed) + { + printf("%lu: failed write\n", tx->transmissionId); + } + else + { + printf("%lu: failed read\n", tx->transmissionId); + } + } + + virtual void txComplete(std::shared_ptr packet, + std::shared_ptr tx) final + { + printf("%lu: complete {", tx->transmissionId); + printf("%08lX", packet->frameWord); + for (std::vector::const_iterator iter = packet->payload.begin(); + iter != packet->payload.end(); + ++iter) + { + printf(" %08lX", *iter); + } + printf("}\n"); + } +} echoTransmitter; + +MaplePassthroughCommandParser::MaplePassthroughCommandParser(std::shared_ptr* schedulers, + const uint8_t* senderAddresses, + uint32_t numSenders) : + mSchedulers(schedulers), + mSenderAddresses(senderAddresses), + mNumSenders(numSenders) +{} + +const char* MaplePassthroughCommandParser::getCommandChars() +{ + // Anything beginning with a hex character should be considered a passthrough command + return "0123456789ABCDEFabcdef"; +} + +void MaplePassthroughCommandParser::submit(const char* chars, uint32_t len) +{ + bool valid = false; + const char* const eol = chars + len; + std::vector words; + const char* iter = chars; + while(iter < eol) + { + uint32_t word = 0; + uint32_t i = 0; + while (i < 8 && iter < eol) + { + char v = *iter++; + uint_fast8_t value = 0; + + if (v >= '0' && v <= '9') + { + value = v - '0'; + } + else if (v >= 'a' && v <= 'f') + { + value = v - 'a' + 0xa; + } + else if (v >= 'A' && v <= 'F') + { + value = v - 'A' + 0xA; + } + else + { + // Ignore this character + continue; + } + + // Apply value into current word + word |= (value << ((8 - i) * 4 - 4)); + ++i; + } + + // Invalid if a partial word was given + valid = ((i == 8) || (i == 0)); + + if (i == 8) + { + words.push_back(word); + } + } + + if (valid) + { + MaplePacket packet(&words[0], words.size()); + if (packet.isValid()) + { + uint8_t sender = packet.getFrameSenderAddr(); + int32_t idx = -1; + const uint8_t* senderAddress = mSenderAddresses; + + for (uint32_t i = 0; i < mNumSenders && idx < 0; ++i, ++senderAddress) + { + if (sender == *senderAddress) + { + idx = i; + } + } + + if (idx >= 0) + { + uint32_t id = mSchedulers[idx]->add( + PrioritizedTxScheduler::EXTERNAL_TRANSMISSION_PRIORITY, + PrioritizedTxScheduler::TX_TIME_ASAP, + &echoTransmitter, + packet, + true); + std::vector::iterator iter = words.begin(); + printf("%lu: added {%08lX", id, *iter++); + for(; iter < words.end(); ++iter) + { + printf(" %08lX", *iter); + } + printf("}\n"); + } + else + { + printf("0: failed invalid sender\n"); + } + } + else + { + printf("0: failed packet invalid\n"); + } + } + else + { + printf("0: failed missing data\n"); + } +} + +void MaplePassthroughCommandParser::printHelp() +{ + printf("0-1 a-f A-F: the beginning of a hex value to send to maple bus without CRC\n"); +} diff --git a/src/coreLib/parsers/MaplePassthroughCommandParser.hpp b/src/coreLib/parsers/MaplePassthroughCommandParser.hpp new file mode 100644 index 0000000..eeef38f --- /dev/null +++ b/src/coreLib/parsers/MaplePassthroughCommandParser.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "hal/Usb/CommandParser.hpp" + +#include "PrioritizedTxScheduler.hpp" + +#include + +// Command structure: [whitespace][command]<\n> + +//! Command parser for processing commands from a TTY stream +class MaplePassthroughCommandParser : public CommandParser +{ +public: + MaplePassthroughCommandParser(std::shared_ptr* schedulers, + const uint8_t* senderAddresses, + uint32_t numSenders); + + //! @returns the string of command characters this parser handles + virtual const char* getCommandChars() final; + + //! Called when newline reached; submit command and reset + virtual void submit(const char* chars, uint32_t len) final; + + //! Prints help message for this command + virtual void printHelp() final; + +private: + std::shared_ptr* const mSchedulers; + const uint8_t* const mSenderAddresses; + const uint32_t mNumSenders; +}; diff --git a/src/hal/Usb/Common/UsbCdcTtyParser.cpp b/src/hal/Usb/Common/UsbCdcTtyParser.cpp new file mode 100644 index 0000000..d519a4d --- /dev/null +++ b/src/hal/Usb/Common/UsbCdcTtyParser.cpp @@ -0,0 +1,154 @@ +#include "UsbCdcTtyParser.hpp" +#include "hal/System/LockGuard.hpp" + +#include +#include +#include + +const char* UsbCdcTtyParser::WHITESPACE_CHARS = "\n\r\t "; + +UsbCdcTtyParser::UsbCdcTtyParser(MutexInterface& m, char helpChar) : + mParserRx(), + mParserMutex(m), + mCommandReady(false), + mHelpChar(helpChar), + mParsers(), + mOverflowDetected(false) +{} + +void UsbCdcTtyParser::addCommandParser(std::shared_ptr parser) +{ + mParsers.push_back(parser); +} + +void UsbCdcTtyParser::addChars(const char* chars, uint32_t len) +{ + // Entire function is locked + LockGuard lockGuard(mParserMutex); + + // Reserve space for new characters + uint32_t newCapacity = mParserRx.size() + len; + if (newCapacity > MAX_QUEUE_SIZE) + { + newCapacity = MAX_QUEUE_SIZE; + } + mParserRx.reserve(newCapacity); + + for (uint32_t i = 0; i < len; ++i, ++chars) + { + // Flag overflow - next command will be ignored + if (mParserRx.size() >= MAX_QUEUE_SIZE && *chars != 0x08) + { + mOverflowDetected = true; + } + + if (mOverflowDetected) + { + if (*chars == '\n') + { + printf("Error: Command input overflow\n"); + // Remove only command that overflowed + std::vector::reverse_iterator lastEnd = + std::find(mParserRx.rbegin(), mParserRx.rend(), '\n'); + if (lastEnd == mParserRx.rend()) + { + mParserRx.clear(); + } + else + { + // We still captured a full command, so at least leave that for processing + mParserRx.erase((lastEnd + 1).base(), mParserRx.end()); + } + mOverflowDetected = false; + } + } + else if (*chars == 0x08) + { + if (!mParserRx.empty()) + { + // Backspace + mParserRx.pop_back(); + } + } + else + { + if (*chars == '\n') + { + mCommandReady = true; + } + mParserRx.push_back(*chars); + } + } +} + +void UsbCdcTtyParser::process() +{ + // Only do something if a command is ready + if (mCommandReady) + { + // Begin lock guard context + LockGuard lockGuard(mParserMutex); + + mCommandReady = false; + + // End of command is at the new line character + std::vector::iterator eol = + std::find(mParserRx.begin(), mParserRx.end(), '\n'); + + if (eol != mParserRx.end()) + { + // Just in case it gets parsed as a string, changed EOL to NULL + *eol = '\0'; + // Move past whitespace characters + const char* ptr = &mParserRx[0]; + uint32_t len = eol - mParserRx.begin(); + while (len > 0 && strchr(WHITESPACE_CHARS, *ptr) != NULL) + { + --len; + ++ptr; + } + + if (len > 0) + { + if (*ptr == mHelpChar) + { + printf("HELP\n" + "Command structure: [whitespace][command]<\\n>\n" + "\n" + "COMMANDS:\n"); + printf("%c: Prints this help\n", mHelpChar); + // Print help for all commands + for (std::vector>::iterator iter = mParsers.begin(); + iter != mParsers.end(); + ++iter) + { + (*iter)->printHelp(); + } + } + else + { + // Find command parser that can process this command + bool processed = false; + for (std::vector>::iterator iter = mParsers.begin(); + iter != mParsers.end() && !processed; + ++iter) + { + if (strchr((*iter)->getCommandChars(), *ptr) != NULL) + { + (*iter)->submit(ptr, len); + processed = true; + } + } + + if (!processed) + { + printf("Error: Invalid command\n"); + } + } + } + // Else: empty string - do nothing + + mParserRx.erase(mParserRx.begin(), eol + 1); + } + } // End lock guard context +} diff --git a/src/hal/Usb/Common/UsbCdcTtyParser.hpp b/src/hal/Usb/Common/UsbCdcTtyParser.hpp new file mode 100644 index 0000000..a1c1ea3 --- /dev/null +++ b/src/hal/Usb/Common/UsbCdcTtyParser.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +#include "hal/System/MutexInterface.hpp" +#include "hal/Usb/TtyParser.hpp" +#include "hal/Usb/CommandParser.hpp" + +// Command structure: [whitespace][command]<\n> + +//! Command parser for processing commands from a TTY stream +class UsbCdcTtyParser : public TtyParser +{ +public: + //! Constructor + UsbCdcTtyParser(MutexInterface& m, char helpChar); + //! Adds a command parser to my list of parsers - must be done before any other function called + virtual void addCommandParser(std::shared_ptr parser) final; + //! Called from the process receiving characters on the TTY + void addChars(const char* chars, uint32_t len); + //! Called from the process handling maple bus execution + virtual void process() final; + +private: + //! Max of 2 KB of memory to use for tty RX queue + static const uint32_t MAX_QUEUE_SIZE = 2048; + //! String of characters that are considered whitespace + static const char* WHITESPACE_CHARS; + //! Receive queue + std::vector mParserRx; + //! Mutex used to serialize addChars and process + MutexInterface& mParserMutex; + //! Flag when end of line detected on add + std::atomic mCommandReady; + //! The command character which prints help for all commands + const char mHelpChar; + //! Parsers that may handle data + std::vector> mParsers; + //! true when overflow in mParserRx + bool mOverflowDetected; +}; diff --git a/src/hal/Usb/Common/cdc.cpp b/src/hal/Usb/Common/cdc.cpp index 98f38d5..697f636 100644 --- a/src/hal/Usb/Common/cdc.cpp +++ b/src/hal/Usb/Common/cdc.cpp @@ -16,11 +16,24 @@ #include "class/hid/hid_device.h" #include "class/cdc/cdc_device.h" -#if CFG_TUD_CDC +#include "UsbCdcTtyParser.hpp" + + +UsbCdcTtyParser* ttyParser = nullptr; + +TtyParser* usb_cdc_create_parser(MutexInterface* m, char helpChar) +{ + if (ttyParser == nullptr) + { + ttyParser = new UsbCdcTtyParser(*m, helpChar); + } + return ttyParser; +} -static MutexInterface* cdcMutex = nullptr; -#if SHOW_DEBUG_MESSAGES +#if CFG_TUD_CDC + +static MutexInterface* stdioMutex = nullptr; // Can't use stdio_usb_init() because it checks tud_cdc_connected(), and that doesn't always return // true when a connection is made. Not all terminal client set this when making connection. @@ -32,7 +45,7 @@ extern "C" { static void stdio_usb_out_chars2(const char *buf, int length) { static uint64_t last_avail_time; - LockGuard lockGuard(*cdcMutex); + LockGuard lockGuard(*stdioMutex); if (!lockGuard.isLocked()) { return; // would deadlock otherwise @@ -65,7 +78,7 @@ static void stdio_usb_out_chars2(const char *buf, int length) { int stdio_usb_in_chars2(char *buf, int length) { - LockGuard lockGuard(*cdcMutex); + LockGuard lockGuard(*stdioMutex); if (!lockGuard.isLocked()) { return PICO_ERROR_NO_DATA; // would deadlock otherwise @@ -92,39 +105,50 @@ struct stdio_driver stdio_usb2 = } // extern "C" -#endif // #if SHOW_DEBUG_MESSAGES - -void cdc_init(MutexInterface* mutex) +void cdc_init(MutexInterface* cdcStdioMutex) { - cdcMutex = mutex; -#if SHOW_DEBUG_MESSAGES + stdioMutex = cdcStdioMutex; stdio_set_driver_enabled(&stdio_usb2, true); -#endif } void cdc_task() { - // This interface is only currently used when SHOW_DEBUG_MESSAGES is true, so no need to do - // anything otherwise -#if CDC_ENABLED - // connected() check for DTR bit - // Most but not all terminal client set this when making connection - // if ( tud_cdc_connected() ) +#if USB_CDC_ENABLED + // connected and there are data available + if (tud_cdc_available()) { - // connected and there are data available - if ( tud_cdc_available() ) + if (ttyParser) { - // TODO: This is where CDC data may be read - tud_cdc_read_flush(); + char buf[64]; + uint32_t count = 0; - // // read datas - // char buf[64]; - // uint32_t count = tud_cdc_read(buf, sizeof(buf)); - // (void) count; + { + LockGuard lockGuard(*stdioMutex); + if (!lockGuard.isLocked()) + { + return; // would deadlock otherwise; just wait for next loop + } + + // read data + count = tud_cdc_read(buf, sizeof(buf)); + + if (count > 0) + { + // Echo back + tud_cdc_write(buf, count); + tud_cdc_write_flush(); + } + } - // // Echo back - // tud_cdc_write(buf, count); - // tud_cdc_write_flush(); + if (count > 0) + { + ttyParser->addChars(buf, count); + } + } + else + { + // Parser not created yet + tud_cdc_read_flush(); } } #endif diff --git a/src/hal/Usb/Common/cdc.hpp b/src/hal/Usb/Common/cdc.hpp index 3da87ab..f5908ed 100644 --- a/src/hal/Usb/Common/cdc.hpp +++ b/src/hal/Usb/Common/cdc.hpp @@ -1,8 +1,9 @@ #include "tusb_config.h" #include "hal/System/MutexInterface.hpp" #include "hal/System/LockGuard.hpp" +#include // CDC is used to create a debug serial interface -void cdc_init(MutexInterface* cdcMutex); +void cdc_init(MutexInterface* cdcStdioMutex); void cdc_task(); diff --git a/src/hal/Usb/Hid/usb_descriptors.c b/src/hal/Usb/Hid/usb_descriptors.c index 1c1ac67..9749b07 100644 --- a/src/hal/Usb/Hid/usb_descriptors.c +++ b/src/hal/Usb/Hid/usb_descriptors.c @@ -112,7 +112,7 @@ uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance) // Configuration Descriptor //--------------------------------------------------------------------+ -#if CDC_ENABLED +#if USB_CDC_ENABLED #define DEBUG_CONFIG_LEN TUD_CDC_DESC_LEN #else #define DEBUG_CONFIG_LEN 0 @@ -171,7 +171,7 @@ uint8_t const desc_configuration[] = // * Communication Device Descriptor (for debug messaging) * // ************************************************************************* -#if CDC_ENABLED +#if USB_CDC_ENABLED // Interface number, string index, EP notification address and size, EP data address (out, in) and size. TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 9, EPIN_CDC_NOTIF, 8, EPOUT_CDC, EPIN_CDC, 64), #endif diff --git a/src/hal/Usb/Hid/usb_descriptors.h b/src/hal/Usb/Hid/usb_descriptors.h index d971c4d..9b194de 100644 --- a/src/hal/Usb/Hid/usb_descriptors.h +++ b/src/hal/Usb/Hid/usb_descriptors.h @@ -3,12 +3,6 @@ #include "configuration.h" -#if SHOW_DEBUG_MESSAGES -#define CDC_ENABLED true -#else -#define CDC_ENABLED false -#endif - // Going in reverse order because the host seems to usually enumerate the highest value first enum { // Gamepads @@ -19,7 +13,7 @@ enum { // For mass storage device ITF_NUM_MSC, -#if CDC_ENABLED +#if USB_CDC_ENABLED ITF_NUM_CDC, ITF_NUM_CDC_DATA, #endif diff --git a/src/hal/Usb/Hid/usb_execution.cpp b/src/hal/Usb/Hid/usb_execution.cpp index 2ad2d65..bbafa6a 100644 --- a/src/hal/Usb/Hid/usb_execution.cpp +++ b/src/hal/Usb/Hid/usb_execution.cpp @@ -125,13 +125,15 @@ void led_task() } -void usb_init(MutexInterface* mscMutex, MutexInterface* cdcMutex) +void usb_init( + MutexInterface* mscMutex, + MutexInterface* cdcStdioMutex) { set_usb_devices(devices, sizeof(devices) / sizeof(devices[1])); board_init(); tusb_init(); msc_init(mscMutex); - cdc_init(cdcMutex); + cdc_init(cdcStdioMutex); #if USB_LED_PIN >= 0 gpio_init(USB_LED_PIN); diff --git a/src/main/main.cpp b/src/main/main.cpp index b287dbd..0ed4e54 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -7,12 +7,16 @@ #include "DreamcastMainNode.hpp" #include "PlayerData.hpp" +#include "MaplePassthroughCommandParser.hpp" + #include "CriticalSectionMutex.hpp" #include "Mutex.hpp" #include "Clock.hpp" +#include "hal/System/LockGuard.hpp" #include "hal/MapleBus/MapleBusInterface.hpp" #include "hal/Usb/usb_interface.hpp" +#include "hal/Usb/TtyParser.hpp" #include #include @@ -42,6 +46,7 @@ void core1() DreamcastControllerObserver** observers = get_usb_controller_observers(); std::shared_ptr buses[numDevices]; std::shared_ptr dreamcastMainNodes[numDevices]; + std::shared_ptr schedulers[numDevices]; Clock clock; for (uint32_t i = 0; i < numDevices; ++i) { @@ -52,19 +57,30 @@ void core1() clock, usb_msc_get_file_system()); buses[i] = create_maple_bus(maplePins[i], MAPLE_HOST_ADDRESSES[i]); + schedulers[i] = std::make_shared(); dreamcastMainNodes[i] = std::make_shared( *buses[i], *playerData[i], - std::make_shared(DreamcastMainNode::MAX_PRIORITY)); + schedulers[i]); } + // Initialize CDC to Maple Bus interfaces + Mutex ttyParserMutex; + TtyParser* ttyParser = usb_cdc_create_parser(&ttyParserMutex, 'h'); + ttyParser->addCommandParser( + std::make_shared( + &schedulers[0], MAPLE_HOST_ADDRESSES, numDevices)); + while(true) { + // Process each main node for (uint32_t i = 0; i < numDevices; ++i) { // Worst execution duration of below is ~350 us at 133 MHz when debug print is disabled dreamcastMainNodes[i]->task(time_us_64()); } + // Process any waiting commands in the TTY parser + ttyParser->process(); } } @@ -81,8 +97,8 @@ int main() multicore_launch_core1(core1); Mutex fileMutex; - Mutex cdcMutex; - usb_init(&fileMutex, &cdcMutex); + Mutex cdcStdioMutex; + usb_init(&fileMutex, &cdcStdioMutex); while(true) { diff --git a/src/test/mocks/MockMutex.hpp b/src/test/mocks/MockMutex.hpp index 1a303d3..7ebb01b 100644 --- a/src/test/mocks/MockMutex.hpp +++ b/src/test/mocks/MockMutex.hpp @@ -11,4 +11,6 @@ class MockMutex : public MutexInterface MOCK_METHOD(void, lock, (), (override)); MOCK_METHOD(void, unlock, (), (override)); + + MOCK_METHOD(int8_t, tryLock, (), (override)); };