From c2d116fa44be20bce8c3726516638d442b0aed9e Mon Sep 17 00:00:00 2001
From: maxgerhardt <maximilian.gerhardt@rub.de>
Date: Fri, 30 Jun 2023 14:44:06 +0200
Subject: [PATCH] Implement better monitor commands for GDB

---
 minichlink/microgdbstub.h | 93 +++++++++++++++++++++++++++++++++++++--
 minichlink/minichgdb.c    |  6 +--
 minichlink/minichlink.c   |  2 +-
 3 files changed, 93 insertions(+), 8 deletions(-)

diff --git a/minichlink/microgdbstub.h b/minichlink/microgdbstub.h
index ef39150..919122e 100644
--- a/minichlink/microgdbstub.h
+++ b/minichlink/microgdbstub.h
@@ -30,7 +30,7 @@ void RVDebugExec( void * dev, int halt_reset_or_resume );
 int RVReadMem( void * dev, uint32_t memaddy, uint8_t * payload, int len );
 int RVHandleBreakpoint( void * dev, int set, uint32_t address );
 int RVWriteRAM(void * dev, uint32_t memaddy, uint32_t length, uint8_t * payload );
-void RVCommandResetPart( void * dev );
+void RVCommandResetPart( void * dev, int mode );
 void RVHandleDisconnect( void * dev );
 void RVHandleGDBBreakRequest( void * dev );
 void RVHandleKillRequest( void * dev );
@@ -106,6 +106,24 @@ static int fromhex( char c )
 	return c;
 }
 
+// output must have length of len / 2.
+static int DecodeHexToBytes(const char* hexstr, size_t string_len, void* output, size_t out_len) {
+	// 2 hex chars make up one byte. out buffer needs to have >= slen/2 bytes.
+	// further, we only want to decode even-length strings
+	if (out_len < (string_len / 2) || (string_len % 2) != 0)
+		return -1;
+	uint8_t* out = (uint8_t*) output;
+	for(size_t i = 0; i < string_len; i += 2) {
+		int nibble1, nibble2;
+		if((nibble1 = fromhex(hexstr[i])) < 0)
+			return nibble1; // error
+		if((nibble2 = fromhex(hexstr[i + 1])) < 0)
+			return nibble2; // error
+		out[i / 2] = ((uint8_t)nibble1 << 4u) | ((uint8_t)nibble2);
+	}
+	return string_len / 2; // number of output bytes written
+}
+
 // if (numhex < 0) 
 static int ReadHex( char ** instr, int numhex, uint32_t * outwrite )
 {
@@ -157,6 +175,19 @@ void SendReplyFull( const char * replyMessage )
 	MicroGDBStubSendReply( replyMessage, -1, '$' );
 }
 
+void MakeGDBPrintText(const char* msg) {
+	// ASCII to hex conversion doubles size, plus 'O', plus NUL
+	size_t buf_len = 2 * strlen(msg) + 2;
+	char* buf = alloca(buf_len);
+	memset(buf, 0, buf_len);
+	buf[0] = 'O'; // for "Output"
+	char* target = buf + 1; 
+	for(size_t i = 0; i < strlen(msg); i++) {
+		target[2*i] = ToHEXNibble((msg[i] & 0xf0u) >> 4u);
+		target[2*i + 1] = ToHEXNibble(msg[i] & 0x0fu);
+	}
+	SendReplyFull(buf);
+}
 ///////////////////////////////////////////////////////////////////////////////
 // General Protocol
 
@@ -191,10 +222,64 @@ void HandleGDBPacket( void * dev, char * data, int len )
 			SendReplyFull( "" );
 		else if( StringMatch( data, "TStatus" ) )  // Trace-Status
 			SendReplyFull( "" );
-		else if( StringMatch( data, "Rcmd,7265736574" ) )  // "monitor reset"
+		else if( StringMatch( data, "Rcmd," ) )  // "monitor <command>"
 		{
-			RVCommandResetPart( dev ); // Force reset
-			SendReplyFull( "+" );
+			// will e.g. be "Rcmd,7265736574#"
+			// hex-decode the rest of it back to ASCII
+			char* cmdHex = data + strlen("Rcmd,");
+			//  check if cmd is empty (no character or only '#' following)
+			if(strlen(cmdHex) > 1) {
+				char cmd[128];
+				memset(cmd, 0, sizeof(cmd));
+				if ( DecodeHexToBytes(cmdHex, strlen(cmdHex) - 1, cmd, sizeof(cmd) - 1) < 0 ) {
+					// decoding failed
+					SendReplyFull( "" );
+					break;
+				}
+				printf("Got monitor command: %s\n", cmd);
+				// Support commands that OpenOCD also does:
+				// https://openocd.org/doc/html/General-Commands.html
+				if(StringMatch(cmd, "halt")) {
+					// only halt
+					RVCommandResetPart( dev, HALT_MODE_HALT_BUT_NO_RESET);
+					SendReplyFull( "+" );
+				}
+				else if(StringMatch(cmd, "reset halt")) {
+					// reset and keep halted after reset
+					RVCommandResetPart( dev, HALT_MODE_HALT_AND_RESET);
+					SendReplyFull( "+" );
+				}
+				else if(StringMatch(cmd, "reset run")) {
+					// reset and run (i.e., reboot)
+					RVCommandResetPart( dev, HALT_MODE_REBOOT);
+					SendReplyFull( "+" );
+				}
+				else if(StringMatch(cmd, "reset")) {
+					// same as reset run per OpenOCD
+					RVCommandResetPart( dev, HALT_MODE_REBOOT);
+					SendReplyFull( "+" );
+				} else if(StringMatch(cmd, "resume")) {
+					// just resume
+					RVCommandResetPart( dev, HALT_MODE_RESUME);
+					SendReplyFull( "+" );
+				} else if(StringMatch(cmd, "help")) {
+					static const char helptext[] = 
+						"minichlink GDB monitor help:\n"
+						"- halt: Halt execution\n"
+						"- resume: Resume execution\n"
+						"- reset [halt, run]: Reset and optionally halt or run\n";
+					MakeGDBPrintText(helptext);
+					SendReplyFull( "+" );
+				}
+				else {
+					printf("Unknown monitor command '%s', use 'monitor help'.\n", cmd);
+					MakeGDBPrintText("Unknown monitor command, use 'monitor help'\n");
+					SendReplyFull( "-" );
+				}
+			} else {
+				MakeGDBPrintText("No monitor command given, use 'monitor help'\n");
+				SendReplyFull( "-" );
+			}
 		}
 		else if( StringMatch( data, "Xfer:memory-map" ) )
 		{
diff --git a/minichlink/minichgdb.c b/minichlink/minichgdb.c
index 2d839fd..774a2f1 100644
--- a/minichlink/minichgdb.c
+++ b/minichlink/minichgdb.c
@@ -71,9 +71,9 @@ void RVCommandEpilogue( void * dev )
 	MCF.WriteReg32( dev, DMDATA0, 0 );
 }
 
-void RVCommandResetPart( void * dev )
+void RVCommandResetPart( void * dev , int mode)
 {
-	MCF.HaltMode( dev, HALT_MODE_HALT_AND_RESET );
+	MCF.HaltMode( dev, mode );
 	RVCommandPrologue( dev );
 }
 
@@ -236,7 +236,7 @@ void RVDebugExec( void * dev, int halt_reset_or_resume )
 			}
 		}
 
-		halt_reset_or_resume = 2;
+		halt_reset_or_resume = HALT_MODE_RESUME;
 	}
 
 	if( shadow_running_state != ( halt_reset_or_resume >= 2 ) )
diff --git a/minichlink/minichlink.c b/minichlink/minichlink.c
index 3314474..14e60e5 100644
--- a/minichlink/minichlink.c
+++ b/minichlink/minichlink.c
@@ -1705,7 +1705,7 @@ static int DefaultHaltMode( void * dev, int mode )
 		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // CFGR (1<<10 == Allow output from slave)
 		MCF.WriteReg32( dev, DMCFGR, 0x5aa50000 | (1<<10) ); // Bug in silicon?  If coming out of cold boot, and we don't do our little "song and dance" this has to be called.
 		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Make the debug module work properly.
-		if( mode == 0 ) MCF.WriteReg32( dev, DMCONTROL, 0x80000003 ); // Reboot.
+		if( mode == HALT_MODE_HALT_AND_RESET ) MCF.WriteReg32( dev, DMCONTROL, 0x80000003 ); // Reboot.
 		MCF.WriteReg32( dev, DMCONTROL, 0x80000001 ); // Re-initiate a halt request.
 //		MCF.WriteReg32( dev, DMCONTROL, 0x00000001 ); // Clear Halt Request.  This is recommended, but not doing it seems more stable.
 		// Sometimes, even if the processor is halted but the MSB is clear, it will spuriously start?
-- 
GitLab