diff --git a/tools/projmgr/include/ProjMgrParser.h b/tools/projmgr/include/ProjMgrParser.h index 9cf78a769..b270dcef4 100644 --- a/tools/projmgr/include/ProjMgrParser.h +++ b/tools/projmgr/include/ProjMgrParser.h @@ -173,6 +173,7 @@ struct CustomItem { * debug protocol (jtag or swd) * debug clock speed * debug configuration file + * device settings * start pname * telnet options * systemview options @@ -183,6 +184,7 @@ struct DebuggerItem { std::string protocol; std::string clock; std::string dbgconf; + std::map deviceSettings; std::string startPname; std::vector telnet; SystemViewItem systemView; diff --git a/tools/projmgr/include/ProjMgrUtils.h b/tools/projmgr/include/ProjMgrUtils.h index 2b2c62eee..988ee63d1 100644 --- a/tools/projmgr/include/ProjMgrUtils.h +++ b/tools/projmgr/include/ProjMgrUtils.h @@ -284,6 +284,13 @@ class ProjMgrUtils { */ static const std::string FormatPath(const std::string& original, const std::string& directory, bool useAbsolutePaths = false); + /** + * @brief parse variable assignments from a dbgconf file + * @param dbgconf path to dbgconf file + * @return map of variable names to assigned values + */ + static std::map ParseDbgconfFile(const std::string& dbgconf); + /** * @brief adjust relative path fragments in a list of values * @param vec list of strings that may contain relative paths diff --git a/tools/projmgr/include/ProjMgrWorker.h b/tools/projmgr/include/ProjMgrWorker.h index c7d299408..e292177f9 100644 --- a/tools/projmgr/include/ProjMgrWorker.h +++ b/tools/projmgr/include/ProjMgrWorker.h @@ -386,6 +386,7 @@ struct TemplateItem { * debug protocol (jtag or swd) * debug clock speed * debug configuration file + * device settings * start pname * list of gdbserver items * list of telnet options items @@ -397,6 +398,7 @@ struct DebuggerType { std::string protocol; std::optional clock; std::string dbgconf; + std::map deviceSettings; std::string startPname; std::vector gdbserver; std::map telnet; diff --git a/tools/projmgr/include/ProjMgrYamlParser.h b/tools/projmgr/include/ProjMgrYamlParser.h index 78e4156e2..13f25aa8e 100644 --- a/tools/projmgr/include/ProjMgrYamlParser.h +++ b/tools/projmgr/include/ProjMgrYamlParser.h @@ -98,6 +98,7 @@ static constexpr const char* YAML_DEBUG_TOPOLOGY = "debug-topology"; static constexpr const char* YAML_DEBUGGER = "debugger"; static constexpr const char* YAML_DEBUGPORTS = "debugports"; static constexpr const char* YAML_DBGCONF = "dbgconf"; +static constexpr const char* YAML_DEVICE_SETTINGS = "device-settings"; static constexpr const char* YAML_DEFAULT = "default"; static constexpr const char* YAML_DEFAULTS = "defaults"; static constexpr const char* YAML_DEFINE = "define"; @@ -370,6 +371,7 @@ class ProjMgrYamlParser { void ParseString(const YAML::Node& parent, const std::string& key, std::string& value); void ParseNumber(const YAML::Node& parent, const std::string& file, const std::string& key, std::string& value); void ParseInt(const YAML::Node& parent, const std::string& key, int& value); + void ParseMapOfStrings(const YAML::Node& parent, const std::string& key, std::map& value); void ParseVector(const YAML::Node& parent, const std::string& key, std::vector& value); void ParseVectorOfStringPairs(const YAML::Node& parent, const std::string& key, std::vector>& value); void ParseVectorOrString(const YAML::Node& parent, const std::string& key, std::vector& value); diff --git a/tools/projmgr/schemas/common.schema.json b/tools/projmgr/schemas/common.schema.json index 7b4b04817..82c4f9261 100644 --- a/tools/projmgr/schemas/common.schema.json +++ b/tools/projmgr/schemas/common.schema.json @@ -2292,11 +2292,18 @@ "gdbserver": { "$ref": "#/definitions/GdbServersType" }, "telnet": { "$ref": "#/definitions/TelnetOptionsType" }, "rtt": { "$ref": "#/definitions/RttOptionsType" }, - "systemview": { "$ref": "#/definitions/SystemViewOptionType" } + "systemview": { "$ref": "#/definitions/SystemViewOptionType" }, + "device-settings": { "$ref": "#/definitions/DeviceSettingsType" } }, "additionalProperties": true, "required": ["name"] }, + "DeviceSettingsType": { + "title": "device-settings:\nDocumentation: https://open-cmsis-pack.github.io/cmsis-toolbox/pyOCD-Debugger/#device-settings", + "description": "Debugger device settings.", + "type": "object", + "additionalProperties": { "type": [ "string", "number", "boolean" ] } + }, "RttOptionsType": { "title": "rtt:\nDocumentation: https://open-cmsis-pack.github.io/cmsis-toolbox/pyOCD-Debugger/#rtt", "description": "Configuration for RTT features.", @@ -2586,7 +2593,8 @@ "start-pname": { "type": "string", "description": "Debugger connects at start to this processor." }, "telnet": { "$ref": "#/definitions/TelnetOptionsType" }, "rtt": { "$ref": "#/definitions/RttOptionsType" }, - "systemview": { "$ref": "#/definitions/SystemViewOptionType" } + "systemview": { "$ref": "#/definitions/SystemViewOptionType" }, + "device-settings": { "$ref": "#/definitions/DeviceSettingsType" } }, "additionalProperties": true, "required": ["name"] diff --git a/tools/projmgr/src/ProjMgrCbuildRun.cpp b/tools/projmgr/src/ProjMgrCbuildRun.cpp index b76dbc0fa..0ff5c9c13 100644 --- a/tools/projmgr/src/ProjMgrCbuildRun.cpp +++ b/tools/projmgr/src/ProjMgrCbuildRun.cpp @@ -24,6 +24,7 @@ class ProjMgrCbuildRun : public ProjMgrCbuildBase { void SetFilesNode(YAML::Node node, const std::vector& outputs); void SetResourcesNode(YAML::Node node, const SystemResourcesType& systemResources); void SetDebuggerNode(YAML::Node node, const DebuggerType& debugger); + void SetDeviceSettingsNode(YAML::Node node, const std::map& deviceSettings); void SetDebugVarsNode(YAML::Node node, const DebugVarsType& debugVars); void SetDebugSequencesNode(YAML::Node node, const std::vector& algorithms); void SetDebugSequencesBlockNode(YAML::Node node, const std::vector& blocks); @@ -187,10 +188,17 @@ void ProjMgrCbuildRun::SetDebuggerNode(YAML::Node node, const DebuggerType& debu SetGdbServerNode(node[YAML_GDBSERVER], debugger.gdbserver); SetTelnetNode(node[YAML_TELNET], debugger.telnet); SetSystemViewNode(node[YAML_SYSTEMVIEW], debugger.systemView); + SetDeviceSettingsNode(node[YAML_DEVICE_SETTINGS], debugger.deviceSettings); SetCustomNodes(node, debugger.custom); } } +void ProjMgrCbuildRun::SetDeviceSettingsNode(YAML::Node node, const map& deviceSettings) { + for (const auto& [key, value] : deviceSettings) { + node[key] = value; + } +} + YAML::Node ProjMgrCbuildRun::GetCustomNode(const CustomItem& value) { YAML::Node node; if (!value.scalar.empty()) { diff --git a/tools/projmgr/src/ProjMgrRunDebug.cpp b/tools/projmgr/src/ProjMgrRunDebug.cpp index bd08669e5..0581c0335 100644 --- a/tools/projmgr/src/ProjMgrRunDebug.cpp +++ b/tools/projmgr/src/ProjMgrRunDebug.cpp @@ -411,6 +411,14 @@ void ProjMgrRunDebug::CollectDebuggerSettings(const ContextItem& context, const m_runDebug.debugger = defaultDebugger; } + // device-settings from dbgconf + if (m_runDebug.debugger.deviceSettings.empty() && !m_runDebug.debugger.dbgconf.empty()) { + m_runDebug.debugger.deviceSettings = ProjMgrUtils::ParseDbgconfFile(m_runDebug.debugger.dbgconf); + } + if (!m_runDebug.debugger.deviceSettings.empty()) { + m_runDebug.debugger.dbgconf.clear(); + } + // primary processor: pname of first cproject if (m_runDebug.debugger.startPname.empty()) { m_runDebug.debugger.startPname = context.deviceItem.pname; @@ -451,7 +459,6 @@ void ProjMgrRunDebug::CollectDebuggerSettings(const ContextItem& context, const // merge custom options MergeCustomItems(context.debugger.custom, m_runDebug.debugger.custom); - } void ProjMgrRunDebug::CollectTelnetOptions(const ContextItem& context, DebugAdapterItem& adapter, diff --git a/tools/projmgr/src/ProjMgrUtils.cpp b/tools/projmgr/src/ProjMgrUtils.cpp index 67a3df26c..702075ecc 100644 --- a/tools/projmgr/src/ProjMgrUtils.cpp +++ b/tools/projmgr/src/ProjMgrUtils.cpp @@ -13,6 +13,7 @@ #include "CrossPlatform.h" #include "CrossPlatformUtils.h" +#include #include #include @@ -22,6 +23,64 @@ static constexpr const char* HIGHER_OR_EQUAL_OPERATOR = ">="; static constexpr const char* TILDE_OPERATOR = "~"; static constexpr const char* CARET_OPERATOR = "^"; +// Dbgconf assignment names follow C-style identifier rules. +static bool IsDbgconfVariableName(const string& value) { + if (value.empty() || (!isalpha(static_cast(value[0])) && value[0] != '_')) { + return false; + } + return all_of(value.begin() + 1, value.end(), [](unsigned char c) { return isalnum(c) || c == '_'; }); +} + +// Remove surrounding whitespace without changing the assignment value content. +static string Trim(const string& value) { + const auto first = find_if_not(value.begin(), value.end(), [](unsigned char c) { return isspace(c); }); + const auto last = find_if_not(value.rbegin(), value.rend(), [](unsigned char c) { return isspace(c); }).base(); + return first < last ? string(first, last) : string(); +} + +// Strip C and C++ comments while preserving line breaks that separate statements. +static string StripCComments(const string& input) { + string output; + bool inLineComment = false; + size_t blockCommentDepth = 0; + size_t index = 0; + while (index < input.length()) { + const char current = input[index]; + const char next = index + 1 < input.length() ? input[index + 1] : '\0'; + + if (inLineComment) { + if (current == '\n' || current == '\r') { + inLineComment = false; + output += current; + } + ++index; + } else if (blockCommentDepth > 0) { + if (current == '/' && next == '*') { + ++blockCommentDepth; + index += 2; + } else if (current == '*' && next == '/') { + --blockCommentDepth; + index += 2; + } else { + if (current == '\n' || current == '\r') { + output += current; + } + ++index; + } + } else if (current == '/' && next == '/') { + inLineComment = true; + index += 2; + } else if (current == '/' && next == '*') { + blockCommentDepth = 1; + index += 2; + } else { + output += current; + ++index; + } + } + return output; +} + RtePackage* ProjMgrUtils::ReadGpdscFile(const string& gpdsc, bool& valid) { fs::path path(gpdsc); error_code ec; @@ -439,6 +498,38 @@ const string ProjMgrUtils::FormatPath(const string& original, const string& dire return path; } +map ProjMgrUtils::ParseDbgconfFile(const string& dbgconf) { + string content; + map assignments; + if (!RteFsUtils::ReadFile(dbgconf, content)) { + return assignments; + } + + const string contentWithoutComments = StripCComments(content); + // Split semicolon-separated statements, also accepting a final unterminated assignment. + size_t statementStart = 0; + size_t statementEnd = contentWithoutComments.find(';'); + while (statementStart < contentWithoutComments.length()) { + const string statement = contentWithoutComments.substr( + statementStart, + statementEnd == string::npos ? string::npos : statementEnd - statementStart); + const size_t assignment = statement.find('='); + if (assignment != string::npos) { + const string key = Trim(statement.substr(0, assignment)); + const string value = Trim(statement.substr(assignment + 1)); + if (IsDbgconfVariableName(key)) { + assignments[key] = value; + } + } + if (statementEnd == string::npos) { + break; + } + statementStart = statementEnd + 1; + statementEnd = contentWithoutComments.find(';', statementStart); + } + return assignments; +} + bool ProjMgrUtils::ContainsIncompatiblePack(const std::list& packs, const std::string& requirement) { bool incompatible = false; for (const auto& p : packs) { diff --git a/tools/projmgr/src/ProjMgrWorker.cpp b/tools/projmgr/src/ProjMgrWorker.cpp index de0c0b1f1..df90e641f 100644 --- a/tools/projmgr/src/ProjMgrWorker.cpp +++ b/tools/projmgr/src/ProjMgrWorker.cpp @@ -2740,6 +2740,7 @@ bool ProjMgrWorker::ProcessDebuggers(ContextItem& context) { } } context.debugger.startPname = m_activeTargetSet.debugger.startPname; + context.debugger.deviceSettings = m_activeTargetSet.debugger.deviceSettings; for (auto telnet : m_activeTargetSet.debugger.telnet) { if (!telnet.file.empty()) { if (!ProcessSequenceRelative(context, telnet.file, context.csolution->directory, false)) { diff --git a/tools/projmgr/src/ProjMgrYamlParser.cpp b/tools/projmgr/src/ProjMgrYamlParser.cpp index 2a052053f..ad7cc8577 100644 --- a/tools/projmgr/src/ProjMgrYamlParser.cpp +++ b/tools/projmgr/src/ProjMgrYamlParser.cpp @@ -402,6 +402,14 @@ void ProjMgrYamlParser::ParseInt(const YAML::Node& parent, const string& key, in } } +void ProjMgrYamlParser::ParseMapOfStrings(const YAML::Node& parent, const string& key, map& value) { + if (parent[key].IsDefined() && parent[key].IsMap()) { + for (const auto& item : parent[key]) { + value[item.first.as()] = YAML::IsNullString(item.second.as()) ? "" : item.second.as(); + } + } +} + void ProjMgrYamlParser::ParseVector(const YAML::Node& parent, const string& key, vector& value) { if (parent[key].IsDefined() && parent[key].IsSequence()) { value = parent[key].as>(); @@ -637,10 +645,11 @@ void ProjMgrYamlParser::ParseDebugger(const YAML::Node& parent, const string& fi ParseString(debuggerNode, YAML_PROTOCOL, debugger.protocol); ParseNumber(debuggerNode, file, YAML_CLOCK, debugger.clock); ParsePortablePath(debuggerNode, file, YAML_DBGCONF, debugger.dbgconf); + ParseMapOfStrings(debuggerNode, YAML_DEVICE_SETTINGS, debugger.deviceSettings); ParseString(debuggerNode, YAML_START_PNAME, debugger.startPname); ParseTelnet(debuggerNode, file, debugger.telnet); ParseSystemView(debuggerNode, file, debugger.systemView); - ParseCustom(debuggerNode, { YAML_NAME, YAML_PROTOCOL, YAML_CLOCK, YAML_DBGCONF, YAML_START_PNAME, YAML_TELNET, YAML_SYSTEMVIEW }, debugger.custom); + ParseCustom(debuggerNode, { YAML_NAME, YAML_PROTOCOL, YAML_CLOCK, YAML_DBGCONF, YAML_DEVICE_SETTINGS, YAML_START_PNAME, YAML_TELNET, YAML_SYSTEMVIEW }, debugger.custom); } } diff --git a/tools/projmgr/test/data/ImageOnly/ref/image-only+CM0.cbuild-run.yml b/tools/projmgr/test/data/ImageOnly/ref/image-only+CM0.cbuild-run.yml index bc115cb46..3fd265a33 100644 --- a/tools/projmgr/test/data/ImageOnly/ref/image-only+CM0.cbuild-run.yml +++ b/tools/projmgr/test/data/ImageOnly/ref/image-only+CM0.cbuild-run.yml @@ -44,9 +44,13 @@ cbuild-run: name: CMSIS-DAP@pyOCD protocol: swd clock: 10000000 - dbgconf: ../.cmsis/image-only+CM0.dbgconf gdbserver: - port: 3333 + device-settings: + ReleaseM0OnConnect: 1 + ReleaseM0SubOnConnect: 1 + RoutingTPIU: 0x00000000 + VecResetWithPeriph: 1 debug-vars: vars: | __var DbgMCU_CR = 0x00000007; // DBGMCU_CR: DBG_SLEEP, DBG_STOP, DBG_STANDBY diff --git a/tools/projmgr/test/data/ImageOnly/ref/image-only-multicore+CM0.cbuild-run.yml b/tools/projmgr/test/data/ImageOnly/ref/image-only-multicore+CM0.cbuild-run.yml index cb66e52b9..afff6f78b 100644 --- a/tools/projmgr/test/data/ImageOnly/ref/image-only-multicore+CM0.cbuild-run.yml +++ b/tools/projmgr/test/data/ImageOnly/ref/image-only-multicore+CM0.cbuild-run.yml @@ -76,13 +76,17 @@ cbuild-run: name: CMSIS-DAP@pyOCD protocol: swd clock: 10000000 - dbgconf: ../.cmsis/image-only-multicore+CM0.dbgconf gdbserver: - port: 3333 - port: 3334 pname: cm0_core0 - port: 3335 pname: cm0_core1 + device-settings: + ReleaseM0OnConnect: 1 + ReleaseM0SubOnConnect: 1 + RoutingTPIU: 0x00000000 + VecResetWithPeriph: 1 debug-vars: vars: | __var DbgMCU_CR = 0x00000007; // DBGMCU_CR: DBG_SLEEP, DBG_STOP, DBG_STANDBY diff --git a/tools/projmgr/test/data/TestRunDebug/ref/custom+TestHW.cbuild-run.yml b/tools/projmgr/test/data/TestRunDebug/ref/custom+TestHW.cbuild-run.yml index 98777c466..202857136 100644 --- a/tools/projmgr/test/data/TestRunDebug/ref/custom+TestHW.cbuild-run.yml +++ b/tools/projmgr/test/data/TestRunDebug/ref/custom+TestHW.cbuild-run.yml @@ -40,7 +40,11 @@ cbuild-run: name: Test Custom Adapter protocol: jtag clock: 40000000 - dbgconf: ../../data/TestRunDebug/.cmsis/custom+TestHW.dbgconf + device-settings: + ReleaseM0OnConnect: 1 + ReleaseM0SubOnConnect: 1 + RoutingTPIU: 0x00000000 + VecResetWithPeriph: 1 custom-adapter-key: custom adapter value custom-key-overwrite: custom value overwrite custom-map: diff --git a/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW.cbuild-run.yml b/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW.cbuild-run.yml index 8be60bf37..d0a472b62 100644 --- a/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW.cbuild-run.yml +++ b/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW.cbuild-run.yml @@ -62,9 +62,11 @@ cbuild-run: info: Info target-set protocol: jtag clock: 20000000 - dbgconf: ../../data/TestRunDebug/RTE/Device/RteTest1/RteTest1.dbgconf gdbserver: - port: 3333 + device-settings: + reset-mode: hardware + trace-port: enabled debug-vars: vars: | __var DbgMCU_CR = 0x00000007; // DBGMCU_CR: DBG_SLEEP, DBG_STOP, DBG_STANDBY diff --git a/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW3.cbuild-run.yml b/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW3.cbuild-run.yml index 81d44bab3..332ec86f1 100644 --- a/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW3.cbuild-run.yml +++ b/tools/projmgr/test/data/TestRunDebug/ref/run-debug+TestHW3.cbuild-run.yml @@ -92,13 +92,17 @@ cbuild-run: name: CMSIS-DAP@pyOCD protocol: swd clock: 10000000 - dbgconf: ../../data/TestRunDebug/.cmsis/run-debug+TestHW3.dbgconf start-pname: cm0_core1 gdbserver: - port: 3333 pname: cm0_core1 - port: 3334 pname: cm0_core0 + device-settings: + ReleaseM0OnConnect: 1 + ReleaseM0SubOnConnect: 1 + RoutingTPIU: 0x00000000 + VecResetWithPeriph: 1 debug-vars: vars: | __var DbgMCU_CR = 0x00000007; // DBGMCU_CR: DBG_SLEEP, DBG_STOP, DBG_STANDBY diff --git a/tools/projmgr/test/data/TestRunDebug/run-debug.csolution.yml b/tools/projmgr/test/data/TestRunDebug/run-debug.csolution.yml index 250e712b1..a25e9fa70 100644 --- a/tools/projmgr/test/data/TestRunDebug/run-debug.csolution.yml +++ b/tools/projmgr/test/data/TestRunDebug/run-debug.csolution.yml @@ -22,6 +22,9 @@ solution: protocol: jtag clock: 20000000 dbgconf: RTE/Device/RteTest1/RteTest1.dbgconf + device-settings: + reset-mode: hardware + trace-port: enabled images: - project-context: run-debug load: none diff --git a/tools/projmgr/test/data/WestSupport/ref/solution+CM0.cbuild-run.yml b/tools/projmgr/test/data/WestSupport/ref/solution+CM0.cbuild-run.yml index 8f3f0ef7f..ecf2e553e 100644 --- a/tools/projmgr/test/data/WestSupport/ref/solution+CM0.cbuild-run.yml +++ b/tools/projmgr/test/data/WestSupport/ref/solution+CM0.cbuild-run.yml @@ -92,13 +92,17 @@ cbuild-run: name: CMSIS-DAP@pyOCD protocol: swd clock: 10000000 - dbgconf: ../.cmsis/solution+CM0.dbgconf start-pname: cm0_core0 gdbserver: - port: 3333 pname: cm0_core0 - port: 3334 pname: cm0_core1 + device-settings: + ReleaseM0OnConnect: 1 + ReleaseM0SubOnConnect: 1 + RoutingTPIU: 0x00000000 + VecResetWithPeriph: 1 debug-vars: vars: | __var DbgMCU_CR = 0x00000007; // DBGMCU_CR: DBG_SLEEP, DBG_STOP, DBG_STANDBY diff --git a/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp b/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp index 617d3eea2..2e4855f5a 100644 --- a/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp +++ b/tools/projmgr/test/src/ProjMgrUtilsUnitTests.cpp @@ -534,6 +534,38 @@ TEST_F(ProjMgrUtilsUnitTests, FormatPath) { } } +TEST_F(ProjMgrUtilsUnitTests, ParseDbgconfFile) { + const string testDir = testoutput_folder + "/ParseDbgconfFile"; + const string dbgconf = testDir + "/test.dbgconf"; + RteFsUtils::CreateDirectories(testDir); + ASSERT_TRUE(RteFsUtils::CreateTextFile(dbgconf, + "// leading comment\n" + "ReleaseM0OnConnect = 1; // trailing comment\n" + "/* block comment with ignored = assignment; */\n" + "RoutingTPIU =\n" + " 0x00000000;\n" + "TracePins = (1 << 18) | 3;\n" + "VecResetWithPeriph /* inline block comment */ = 1;\n" + "NestedBlockComment = 3 /* outer /* inner */ still outer */;\n" + "LineCommentInBlock = 4 /* // not a line comment\n" + " still block comment */;\n" + "BlockCommentInLine = 5; // /* not a block comment */\n" + "MissingTerminator = 2\n")); + + const map expected = { + {"ReleaseM0OnConnect", "1"}, + {"RoutingTPIU", "0x00000000"}, + {"TracePins", "(1 << 18) | 3"}, + {"VecResetWithPeriph", "1"}, + {"NestedBlockComment", "3"}, + {"LineCommentInBlock", "4"}, + {"BlockCommentInLine", "5"}, + {"MissingTerminator", "2"}, + }; + EXPECT_EQ(expected, ProjMgrUtils::ParseDbgconfFile(dbgconf)); + EXPECT_TRUE(ProjMgrUtils::ParseDbgconfFile(testDir + "/missing.dbgconf").empty()); +} + TEST_F(ProjMgrUtilsUnitTests, ContainsIncompatiblePack) { ProjMgrKernel::Get()->SetCmsisPackRoot(testcmsispack_folder); std::list pdscFiles;