diff --git a/examples/blink/.vscode/launch.json b/examples/blink/.vscode/launch.json
new file mode 100644
index 0000000000000000000000000000000000000000..daf0b515adfa0bbf74d743499abafb8cb48dab64
--- /dev/null
+++ b/examples/blink/.vscode/launch.json
@@ -0,0 +1,25 @@
+{
+	"configurations": [
+	{
+		"name": "GDB debug - custom",
+		"type": "cppdbg",
+		"request": "launch",
+		"program": "blink.elf",
+		"args": [],
+		"stopAtEntry": true,
+		"cwd": "${workspaceFolder}",
+		"environment": [],
+		"externalConsole": false,
+		"MIMode": "gdb",
+		"setupCommands": [
+		{
+			"description": "Enable pretty-printing for gdb",
+			"text": "-enable-pretty-printing",
+			"ignoreFailures": true
+		}
+		],
+		"miDebuggerPath": "/usr/bin/gdb-multiarch",
+		"miDebuggerServerAddress": "127.0.0.1:2000"
+	}
+	]
+}
diff --git a/examples/blink/.vscode/settings.json b/examples/blink/.vscode/settings.json
new file mode 100644
index 0000000000000000000000000000000000000000..79b30b8432213edaa2ffffd7b366b7cb61bf8240
--- /dev/null
+++ b/examples/blink/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+    "cmake.configureOnOpen": false,
+    "makefile.launchConfigurations": [
+        {
+            "cwd": "/home/cnlohr/git/ch32v003fun/examples/blink",
+            "sbinaryPath": "/home/cnlohr/git/ch32v003fun/examples/blink/blink.elf",
+            "binaryArgs": []
+        }
+    ],
+    "editor.insertSpaces": false,
+    "editor.tabSize": 4
+}
diff --git a/examples/blink/blink.bin b/examples/blink/blink.bin
index b0cf5a4fa20b888e4185c78fd1a5230786bc2059..cd9973989add95d2e43f0ad61d045a2bd32d14e3 100755
Binary files a/examples/blink/blink.bin and b/examples/blink/blink.bin differ
diff --git a/examples/blink/blink.c b/examples/blink/blink.c
index e65639989d63a04cef910d2ae4fd5b238e15407e..4646259b1352778b1a5985f335614ab3e91e477d 100644
--- a/examples/blink/blink.c
+++ b/examples/blink/blink.c
@@ -6,7 +6,7 @@
 
 #define APB_CLOCK SYSTEM_CORE_CLOCK
 
-uint32_t count;
+volatile uint32_t count;
 
 int main()
 {
@@ -34,8 +34,9 @@ int main()
 		Delay_Ms( 250 );
 		GPIOD->BSHR = (1<<16) | (1<<(16+4)); // Turn off GPIODs
 		GPIOC->BSHR = (1<<16);
-		Delay_Ms( 250 );
+		Delay_Ms( count );
 		count++;
+		if( count > 250 ) count = 0;
 	}
 }
 
diff --git a/minichlink/Makefile b/minichlink/Makefile
index 3a367d436565111d73007bd1a2d399ad87f4493a..0bd37d30107a55504282a51cafd0406d905875f9 100644
--- a/minichlink/Makefile
+++ b/minichlink/Makefile
@@ -4,6 +4,12 @@ all : $(TOOLS)
 
 CFLAGS:=-O0 -g3 -Wall
 
+C_S:=minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c minichgdb.c
+
+# General Note: To use with GDB, gdb-multiarch
+# gdb-multilib {file}
+# target remote :2345
+
 ifeq ($(OS),Windows_NT)
 	LDFLAGS:=-lpthread -lusb-1.0 -lsetupapi
 else
@@ -20,13 +26,14 @@ else
 	endif
 endif
 
+# will need mingw-w64-x86-64-dev gcc-mingw-w64-x86-64
+minichlink.exe : $(C_S)
+	x86_64-w64-mingw32-gcc -o $@ $^ -Os -s -lsetupapi ./libusb-1.0.dll
 
-
-
-minichlink : minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c
+minichlink : $(C_S)
 	gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS)
 
-minichlink.so : minichlink.c pgm-wch-linke.c pgm-esp32s2-ch32xx.c nhc-link042.c
+minichlink.so : $(C_S)
 	gcc -o $@ $^ $(LDFLAGS) $(CFLAGS) $(INCS) -shared -fPIC
 
 install_udev_rules :
diff --git a/minichlink/microgdbstub.h b/minichlink/microgdbstub.h
new file mode 100644
index 0000000000000000000000000000000000000000..669c0806ff1633b8dbcf8f51f3569ebe92e77d18
--- /dev/null
+++ b/minichlink/microgdbstub.h
@@ -0,0 +1,624 @@
+/* 
+ * Micro GDBStub Driver, for implementing a gdbserver.
+ * Copyright (C) Charles Lohr 2023
+ *  You may freely license this file under the MIT-x11, or the 2- or 3- or New BSD Licenses.
+ *  You may also use this as though it is public domain.
+ *
+ * This project is based off of picorvd. https://github.com/aappleby/PicoRVD/
+ *
+ * Simply:
+ *   1: define the RV_ Functions
+ *   2: Call the MicroGDB* functions needed.
+ *   3: Define MICROGDBSTUB_IMPLEMENTATION at least in one place this is included in your program.
+ *   4: If you want to let this manage the server as a network device, simply #define MICROGDBSTUB_SOCKETS
+ *
+ * To connect to your GDBStub running, you can:
+ *  gdb-multiarch -ex 'target remote :2000' ./blink.elf 
+ *
+ */
+
+#ifndef _MICROGDBSTUB_H
+#define _MICROGDBSTUB_H
+
+// You must write these for your processor.
+void RVNetPoll(void * dev );
+int RVSendGDBHaltReason( void * dev );
+void RVNetConnect( void * dev );
+int RVSendGDBHaltReason( void * dev );
+void RVNetPoll(void * dev );
+int RVReadCPURegister( void * dev, int regno, uint32_t * regret );
+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 RVHandleDisconnect( void * dev );
+void RVHandleGDBBreakRequest( void * dev );
+
+#ifdef MICROGDBSTUB_SOCKETS
+int MicroGDBPollServer( void * dev );
+int MicroGDBStubStartup( void * dev );
+void MicroGDBExitServer( void * dev );
+#endif
+
+// If you are not a network socket, you can pass in this data.
+void MicroGDBStubSendReply( const void * data, int len, int docs );
+void MicroGDBStubHandleClientData( void * dev, const uint8_t * rxdata, int len );
+
+
+#ifdef MICROGDBSTUB_IMPLEMENTATION
+
+///////////////////////////////////////////////////////////////////////////////
+// Protocol Stuff
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+uint16_t htons(uint16_t hostshort);
+uint32_t htonl(uint32_t hostlong);
+
+char gdbbuffer[65536];
+uint8_t gdbchecksum = 0;
+int gdbbufferplace = 0;
+int gdbbufferstate = 0;
+
+
+static inline char ToHEXNibble( int i )
+{
+	i &= 0xf;
+	return ( i < 10 )?('0' + i):('a' - 10 + i);
+}
+
+static int fromhex( char c )
+{
+	if( c >= '0' && c <= '9' ) c = c - '0';
+	else if( c >= 'A' && c <= 'F' ) c = c - 'A' + 10;
+	else if( c >= 'a' && c <= 'f' ) c = c - 'a' + 10;
+	else return -1;
+	return c;
+}
+
+// if (numhex < 0) 
+static int ReadHex( char ** instr, int numhex, uint32_t * outwrite )
+{
+	if( !instr ) return -1;
+	char * str = *instr;
+	// If negative - error.
+	// If positive - number of bytes read.
+
+	*outwrite = 0;
+
+	int scanhex = numhex;
+	if( scanhex < 0 )
+		scanhex = strlen( str );
+
+	int i;
+	for( i = 0; i < scanhex; i++ )
+	{
+		int v = fromhex( *(str++) );
+		if( v < 0 )
+		{
+			if( numhex < 0 )
+			{
+				str--;
+				*instr = str;
+				return i;
+			}
+			else
+			{
+				*instr = str;
+				return - i - 1;
+			}
+		}
+		(*outwrite) = ((*outwrite) << 4) | v;
+	}
+	*instr = str;
+	return i;
+}
+
+static int StringMatch( const char * haystack, const char * mat )
+{
+	int i;
+	for( i = 0; mat[i] && haystack[i]; i++ )
+		if( mat[i] != haystack[i] || haystack[i] == 0 ) break;
+	return mat[i] == 0;
+}
+
+void SendReplyFull( const char * replyMessage )
+{
+	MicroGDBStubSendReply( replyMessage, -1, '$' );
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// General Protocol
+
+void HandleGDBPacket( void * dev, char * data, int len )
+{
+	int i;
+
+	//printf( ":::%s:::\n", data );
+	// Got a packet?
+	if( data[0] != '$' ) return;
+
+	data++;
+
+	char cmd = *(data++);
+	switch( cmd )
+	{
+	case 'q':
+		if( StringMatch( data, "Attached" ) )
+		    SendReplyFull( "1" ); //Attached to an existing process.
+		else if( StringMatch( data, "Supported" ) )
+		    SendReplyFull( "PacketSize=f000;qXfer:memory-map:read+" );
+		else if( StringMatch( data, "C") ) // Get Current Thread ID. (Can't be -1 or 0.  Those are special)
+		    SendReplyFull( "QC1" );
+		else if( StringMatch( data, "fThreadInfo" ) )  // Query all active thread IDs (Can't be 0 or 1)
+			SendReplyFull( "m1" );
+		else if( StringMatch( data, "sThreadInfo" ) )  // Query all active thread IDs, continued
+		    SendReplyFull( "l" );
+		else if( StringMatch( data, "Xfer:memory-map" ) )
+		    SendReplyFull( MICROGDBSTUB_MEMORY_MAP );
+		else
+			SendReplyFull( "" );
+		break;
+	case 'c':
+	case 'C':
+		RVDebugExec( dev, (cmd == 'C')?4:2 );
+		SendReplyFull( "OK" );
+		break;
+	case 'D':
+		// Handle disconnect.
+		RVHandleDisconnect( dev );
+		break;
+	case 'Z':
+	case 'z':
+	{
+		uint32_t type = 0;
+		uint32_t addr = 0;
+		uint32_t time = 0;
+		if( ReadHex( &data, -1, &type ) < 0 ) goto err;
+		if( *(data++) != ',' ) goto err;
+		if( ReadHex( &data, -1, &addr ) < 0 ) goto err;
+		if( *(data++) != ',' ) goto err;
+		if( ReadHex( &data, -1, &time ) < 0 ) goto err;
+		if( RVHandleBreakpoint( dev, cmd == 'Z', addr ) == 0 )
+		{
+			SendReplyFull( "OK" );
+		}
+		else
+			goto err;
+		break;
+	}
+	case 'm':
+	{
+		// Read memory (Binary)
+		uint32_t address_to_read = 0;
+		uint32_t length_to_read = 0;
+		if( ReadHex( &data, -1, &address_to_read ) < 0 ) goto err;
+		if( *(data++) != ',' ) goto err;
+		if( ReadHex( &data, -1, &length_to_read ) < 0 ) goto err;
+
+		uint8_t * pl = alloca( length_to_read * 2 );
+		if( RVReadMem( dev, address_to_read, pl, length_to_read ) < 0 )
+			goto err;
+		char * repl = alloca( length_to_read * 2 );
+		int i;
+		for( i = 0; i < length_to_read; i++ )
+		{
+			sprintf( repl + i * 2, "%02x", pl[i] );
+		}
+		SendReplyFull( repl );
+		break;
+	}
+	case 'M':
+	{
+		uint32_t address_to_write = 0;
+		uint32_t length_to_write = 0;
+		if( ReadHex( &data, -1, &address_to_write ) < 0 ) goto err;
+		if( *(data++) != ',' ) goto err;
+		if( ReadHex( &data, -1, &length_to_write ) < 0 ) goto err;
+		if( *(data++) != ':' ) goto err;
+
+		uint8_t * meml = alloca( length_to_write );
+		int i;
+		for( i = 0; i < length_to_write; i++ )
+		{
+			uint32_t rv;
+			if( ReadHex( &data, 2, &rv ) < 0 ) goto err;
+			meml[i] = rv;
+		}
+		if( RVWriteRAM( dev, address_to_write, length_to_write, meml ) < 0 ) goto err;
+		SendReplyFull( "OK" );
+		break;
+	}
+	case 'X':
+	{
+		// Write memory, binary.
+		uint32_t address_to_write = 0;
+		uint32_t length_to_write = 0;
+		if( ReadHex( &data, -1, &address_to_write ) < 0 ) goto err;
+		if( *(data++) != ',' ) goto err;
+		if( ReadHex( &data, -1, &length_to_write ) < 0 ) goto err;
+		if( *(data++) != ':' ) goto err;
+		if( RVWriteRAM( dev, address_to_write, length_to_write, (uint8_t*)data ) < 0 ) goto err;
+		SendReplyFull( "OK" );
+		break;
+	}
+	case 'v':
+		if( StringMatch( data, "Cont?" ) )
+		{
+			// Request a list of actions supported by the ‘vCont’ packet. 
+			// We don't support vCont
+			SendReplyFull( "vCont;s;c;t;" ); //no ;s
+			//SendReplyFull( "" );
+		}
+		else
+		{
+			SendReplyFull( "" ); //"vMustReplyEmpty"
+		}
+		break;
+	case 'g':
+	{
+		// Register Read (All regs)
+		char cts[17*8+1];
+		for( i = 0; i < 17; i++ )
+		{
+			uint32_t regret;
+			if( RVReadCPURegister( dev, i, &regret ) ) goto err;
+			sprintf( cts + i * 8, "%08x", htonl( regret ) );
+		}
+		SendReplyFull( cts );
+		break;
+	}
+	case 'p':
+	{
+		uint32_t regno;
+		// Register Read (Specific Reg)
+		if( ReadHex( &data, 2, &regno ) < 0 )
+			SendReplyFull( "E 10" );
+		else
+		{
+			char cts[9];
+			uint32_t regret;
+			if( RVReadCPURegister( dev, regno, &regret ) ) goto err;
+			sprintf( cts, "%08x", htonl( regret ) );
+			SendReplyFull( cts );
+		}
+		break;
+	}
+	case '?': // Query reason for target halt.
+		RVSendGDBHaltReason( dev );
+		break;
+	case 'H':
+		// This is for things like selecting threads.
+		// I am going to leave this stubbed out.
+		SendReplyFull( "" );
+		break;
+	default:
+		printf( "UNKNOWN PACKET: %d (%s)\n", len, data-1 );
+		for( i = 0; i < len; i++ )
+		{
+			printf( "%02x ", data[i] );
+		}
+		printf( "\n" );
+		goto err;
+		break;
+	}
+	return;
+err:
+	SendReplyFull( "E 00" );
+}
+
+void MicroGDBStubHandleClientData( void * dev, const uint8_t * rxdata, int len )
+{
+	int pl = 0;
+	for( pl = 0; pl < len; pl++ )
+	{
+		int c = rxdata[pl];
+		if( c == '$' && gdbbufferstate == 0 )
+		{
+			gdbbufferplace = 0;
+			gdbbufferstate = 1;
+		}
+		if( c == 3 && gdbbufferstate == 0 )
+		{
+			RVHandleGDBBreakRequest( dev );
+			continue;
+		}
+
+		switch( gdbbufferstate )
+		{
+		default:
+			break;
+		case 1:
+			if( gdbbufferplace < sizeof( gdbbuffer ) - 2 )
+			{
+				if( c == '}' ) { gdbbufferstate = 9; break; }
+				gdbbuffer[gdbbufferplace++] = c;
+			}
+			if( c == '#' ) gdbbufferstate = 2;
+			break;
+		case 9: // escape
+			gdbbuffer[gdbbufferplace++] = c ^ 0x20;
+			break;
+		case 2:
+		case 3:
+		{
+			int i;
+			c = fromhex( c );
+			if( gdbbufferstate == 2 )
+			{
+				gdbchecksum = c << 4;
+				gdbbufferstate++;
+				break; 
+			}
+			if( gdbbufferstate == 3 ) gdbchecksum |= c;
+			
+			gdbbuffer[gdbbufferplace] = 0;
+
+			uint8_t checkcsum = 0;
+			for( i = 1; i < gdbbufferplace - 1; i++ )
+			{
+				checkcsum += ((uint8_t*)gdbbuffer)[i];
+			}
+			if( checkcsum == gdbchecksum )
+			{
+				MicroGDBStubSendReply( "+", -1, 0 );
+				HandleGDBPacket( dev, (char*)gdbbuffer, gdbbufferplace );
+			}
+			else
+			{
+				MicroGDBStubSendReply( "-", -1, 0 );
+			}
+
+			gdbbufferplace = 0;
+			gdbbufferstate = 0;
+			break;
+		}
+		}
+	}
+}
+
+
+
+
+#ifdef MICROGDBSTUB_SOCKETS
+
+
+#ifdef WIN32
+#include <winsock2.h>
+#define socklen_t uint32_t
+#define SHUT_RDWR SD_BOTH
+#define MSG_NOSIGNAL 0
+#else
+#define closesocket close
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <linux/in.h>
+#endif
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <poll.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <stdlib.h>
+
+
+int listenMode; // 0 for uninit.  1 for server, 2 for client.
+int serverSocket;
+
+///////////////////////////////////////////////////////////////////////////////
+// Network layer.
+
+
+void MicroGDBStubHandleDisconnect( void * dev )
+{
+	RVHandleDisconnect( dev );
+}
+
+static int GDBListen( void * dev )
+{
+	struct	sockaddr_in sin;
+	serverSocket = socket(AF_INET, SOCK_STREAM, 0);
+
+	//Make sure the socket worked.
+	if( serverSocket == -1 )
+	{
+		fprintf( stderr, "Error: Cannot create socket.\n" );
+		return -1;
+	}
+
+	//Disable SO_LINGER (Well, enable it but turn it way down)
+#ifdef WIN32
+	struct linger lx;
+	lx.l_onoff = 1;
+	lx.l_linger = 0;
+	setsockopt( serverSocket, SOL_SOCKET, SO_LINGER, (const char *)&lx, sizeof( lx ) );
+
+	//Enable SO_REUSEADDR
+	int reusevar = 1;
+	setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reusevar, sizeof(reusevar));
+#else
+	struct linger lx;
+	lx.l_onoff = 1;
+	lx.l_linger = 0;
+	setsockopt( serverSocket, SOL_SOCKET, SO_LINGER, &lx, sizeof( lx ) );
+
+	//Enable SO_REUSEADDR
+	int reusevar = 1;
+	setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &reusevar, sizeof(reusevar));
+#endif
+	//Setup socket for listening address.
+	memset( &sin, 0, sizeof( sin ) );
+	sin.sin_family = AF_INET;
+	sin.sin_addr.s_addr = INADDR_ANY;
+	sin.sin_port = htons( MICROGDBSTUB_PORT );
+
+	//Actually bind to the socket
+	if( bind( serverSocket, (struct sockaddr *) &sin, sizeof( sin ) ) == -1 )
+	{
+		fprintf( stderr, "Could not bind to socket: %d\n", MICROGDBSTUB_PORT );
+		closesocket( serverSocket );
+		serverSocket = 0;
+		return -1;
+	}
+
+	//Finally listen.
+	if( listen( serverSocket, 5 ) == -1 )
+	{
+		fprintf(stderr, "Could not lieten to socket.");
+		closesocket( serverSocket );
+		serverSocket = 0;
+		return -1;
+	}
+	return 0;
+}
+
+int MicroGDBPollServer( void * dev )
+{
+	if( !serverSocket ) return -4;
+
+	struct pollfd allpolls[2];
+
+	int pollct = 1;
+	allpolls[0].fd = serverSocket;
+	allpolls[0].events = POLLIN;
+
+	//Do something to watch all currently-waiting sockets.
+	poll( allpolls, pollct, 0 );
+
+	//If there's faults, bail.
+	if( allpolls[0].revents & (POLLERR|POLLHUP) )
+	{
+		closesocket( serverSocket );
+		if( listenMode == 1 )
+		{
+			// Some sort of weird fatal close?  Is this even possible?
+			fprintf( stderr, "Error: serverSocke was forcibly closed\n" );
+			exit( -4 );
+		}
+		else if( listenMode == 2 )
+		{
+			MicroGDBStubHandleDisconnect( dev );
+			if( serverSocket ) 	close( serverSocket );
+			serverSocket = 0;
+			listenMode = 1;
+			GDBListen( dev );
+		}
+	}
+	if( allpolls[0].revents & POLLIN )
+	{
+		if( listenMode == 1 )
+		{
+			struct   sockaddr_in tin;
+			socklen_t addrlen  = sizeof(tin);
+			memset( &tin, 0, addrlen );
+			int tsocket = accept( serverSocket, (struct sockaddr *)&tin, &addrlen );
+			closesocket( serverSocket );
+			serverSocket = tsocket;
+			listenMode = 2;
+			gdbbufferstate = 0;
+			RVNetConnect( dev );
+			// Established.
+		}
+		else if( listenMode == 2 )
+		{
+			// Got data from a peer.
+			uint8_t buffer[16384];
+			ssize_t rx = recv( serverSocket, buffer, sizeof( buffer ), MSG_NOSIGNAL );
+			if( rx == 0 )
+			{
+				MicroGDBStubHandleDisconnect( dev );
+				close( serverSocket );
+				serverSocket = 0;
+				listenMode = 1;
+				GDBListen( dev );
+			}
+			else
+				MicroGDBStubHandleClientData( dev, buffer, (int)rx );
+		}
+	}
+
+	if( listenMode == 2 )
+	{
+		RVNetPoll( dev );
+	}
+
+	return 0;
+}
+
+void MicroGDBExitServer( void * dev )
+{
+	shutdown( serverSocket, SHUT_RDWR );
+	if( listenMode == 2 )
+	{
+		MicroGDBStubHandleDisconnect( dev );
+	}
+}
+
+
+void MicroGDBStubSendReply( const void * data, int len, int docs )
+{
+	if( len < 0 ) len = strlen( data );
+	if( docs )
+	{
+		uint8_t * localbuffer = alloca( len ) + 5;
+		localbuffer[0] = '$';
+		uint8_t checksum = 0;
+		int i;
+		for( i = 0; i < len; i++ )
+		{
+			uint8_t v = ((const uint8_t*)data)[i];
+			checksum += v;
+			localbuffer[i+1] = v;
+		}
+		localbuffer[len+1] = '#';
+		localbuffer[len+2] = ToHEXNibble( checksum >> 4 );
+		localbuffer[len+3] = ToHEXNibble( checksum );
+		localbuffer[len+4] = 0;
+		data = (void*)localbuffer;
+		len += 4;
+	}
+	
+	if( listenMode == 2 )
+	{
+		//printf( ">>>>%s<<<<(%d)\n", data );
+		send( serverSocket, data, len, MSG_NOSIGNAL );
+	}
+}
+
+
+int MicroGDBStubStartup( void * dev )
+{
+#ifdef WIN32
+{
+    WORD wVersionRequested;
+    WSADATA wsaData;
+    int err;
+    wVersionRequested = MAKEWORD(2, 2);
+
+    err = WSAStartup(wVersionRequested, &wsaData);
+    if (err != 0) {
+        /* Tell the user that we could not find a usable */
+        /* Winsock DLL.                                  */
+        fprintf( stderr, "WSAStartup failed with error: %d\n", err);
+        return 1;
+    }
+}
+#endif
+
+	listenMode = 1;
+
+	return GDBListen( dev );
+}
+
+
+#endif
+#endif
+
+#endif
+
diff --git a/minichlink/minichgdb.c b/minichlink/minichgdb.c
new file mode 100644
index 0000000000000000000000000000000000000000..c3f783a6c00c200f48cb03b2128c6bc1646a2aa6
--- /dev/null
+++ b/minichlink/minichgdb.c
@@ -0,0 +1,344 @@
+// This file is loosely based on aappleby's GDBServer.
+
+// Connect in with:
+//   gdb-multiarch -ex 'target remote :2000' ./blink.elf 
+
+#include "minichlink.h"
+
+#define MICROGDBSTUB_IMPLEMENTATION
+#define MICROGDBSTUB_SOCKETS
+#define MICROGDBSTUB_PORT 2000
+
+
+const char* MICROGDBSTUB_MEMORY_MAP = "l<?xml version=\"1.0\"?>"
+"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\" \"http://sourceware.org/gdb/gdb-memory-map.dtd\">"
+"<memory-map>"
+"  <memory type=\"flash\" start=\"0x00000000\" length=\"0x4000\">"
+"    <property name=\"blocksize\">64</property>"
+"  </memory>"
+"  <memory type=\"ram\" start=\"0x20000000\" length=\"0x800\">"
+"    <property name=\"blocksize\">4</property>"
+"  </memory>"
+"</memory-map>";
+
+#include "microgdbstub.h"
+
+void SendReplyFull( const char * replyMessage );
+
+///////////////////////////////////////////////////////////////////////////////
+// Actual Chip Operations
+
+// Several pieces from picorvd. https://github.com/aappleby/PicoRVD/
+int shadow_running_state = 1;
+int last_halt_reason = 5;
+uint32_t backup_regs[17];
+
+#define MAX_SOFTWARE_BREAKPOINTS 128
+int num_software_breakpoints = 0;
+uint8_t  software_breakpoint_type[MAX_SOFTWARE_BREAKPOINTS]; // 0 = not in use, 1 = 32-bit, 2 = 16-bit.
+uint32_t software_breakpoint_addy[MAX_SOFTWARE_BREAKPOINTS];
+uint32_t previous_word_at_breakpoint_address[MAX_SOFTWARE_BREAKPOINTS];
+
+int IsGDBServerInShadowHaltState( void * dev ) { return !shadow_running_state; }
+
+void RVCommandPrologue( void * dev )
+{
+	if( !MCF.ReadCPURegister )
+	{
+		fprintf( stderr, "Error: Programmer does not support register reading\n" );
+		exit( -5 );
+	}
+
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0 );     // Disable autoexec.
+	if( MCF.ReadAllCPURegisters( dev, backup_regs ) )
+	{
+		fprintf( stderr, "WARNING: failed to preserve registers\n" );
+	}
+	MCF.VoidHighLevelState( dev );
+}
+
+void RVCommandEpilogue( void * dev )
+{
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0 );   // Disable autoexec.
+	MCF.WriteAllCPURegisters( dev, backup_regs );
+	MCF.VoidHighLevelState( dev );
+}
+
+void RVNetConnect( void * dev )
+{
+	// ??? Should we actually halt?
+	MCF.HaltMode( dev, 0 );
+	MCF.SetEnableBreakpoints( dev, 1, 0 );
+	RVCommandPrologue( dev );
+	shadow_running_state = 0;
+}
+
+int RVSendGDBHaltReason( void * dev )
+{
+	char st[5];
+	sprintf( st, "T%02x", last_halt_reason );
+	SendReplyFull( st );
+	return 0;
+}
+
+void RVNetPoll(void * dev )
+{
+	if( !MCF.ReadReg32 )
+	{
+		fprintf( stderr, "Error: Can't poll GDB because no ReadReg32 supported on this programmer\n" );
+		return;
+	}
+
+	uint32_t status;
+	if( MCF.ReadReg32( dev, DMSTATUS, &status ) )
+	{
+		fprintf( stderr, "Error: Could not get part status\n" );
+		return;
+	}
+	int statusrunning = ((status & (1<<10)));
+
+	static int laststatus;
+	if( status != laststatus )
+	{
+		//printf( "DMSTATUS: %08x => %08x\n", laststatus, status );
+		laststatus = status;
+	}
+	if( statusrunning != shadow_running_state )
+	{
+		// If was running but now is halted.
+		if( statusrunning == 0 )
+		{
+			RVCommandPrologue( dev );
+			//uint32_t dscr;
+			//MCF.ReadCPURegister( dev, 0x7b0, &dscr );
+			last_halt_reason = 5;//((dscr>>6)&3)+5;
+			RVSendGDBHaltReason( dev );
+		}
+		shadow_running_state = statusrunning;
+	}
+}
+
+int RVReadCPURegister( void * dev, int regno, uint32_t * regret )
+{
+	if( shadow_running_state )
+	{
+		MCF.HaltMode( dev, 0 );
+		RVCommandPrologue( dev );
+		shadow_running_state = 0;
+	}
+
+	if( regno == 32 ) regno = 16; // Hack - Make 32 also 16 for old GDBs.
+	if( regno > 16 ) return 0; // Invalid register.
+
+	*regret = backup_regs[regno];
+	return 0;
+}
+
+void RVDebugExec( void * dev, int halt_reset_or_resume )
+{
+	if( !MCF.HaltMode )
+	{
+		fprintf( stderr, "Error: Can't alter halt mode with this programmer.\n" );
+		exit( -6 );
+	}
+
+	// Special case halt_reset_or_resume = 4: Skip instruction and resume.
+	if( halt_reset_or_resume == 4 || halt_reset_or_resume == 2 )
+	{
+		// For this we want to advance PC.
+		uint32_t exceptionptr = backup_regs[16];
+		uint32_t instruction = 0;
+		if( exceptionptr & 2 )
+		{
+			uint32_t part1, part2;
+			MCF.ReadWord( dev, exceptionptr & ~3, &part1 );
+			MCF.ReadWord( dev, (exceptionptr & ~3)+4, &part2 );
+			instruction = (part1 >> 16) | (part2 << 16);
+		}
+		else
+		{
+			MCF.ReadWord( dev, exceptionptr, &instruction );
+		}
+		if( instruction == 0x00100073 )
+			backup_regs[16]+=4;
+		else if( ( instruction & 0xffff ) == 0x9002 )
+			backup_regs[16]+=2;
+		halt_reset_or_resume = 2;
+	}
+
+	if( shadow_running_state != ( halt_reset_or_resume >= 2 ) )
+	{
+		if( halt_reset_or_resume < 2 )
+		{
+			RVCommandPrologue( dev );
+		}
+		else
+		{
+			RVCommandEpilogue( dev );
+		}
+		MCF.HaltMode( dev, halt_reset_or_resume );
+	}
+
+	shadow_running_state = halt_reset_or_resume >= 2;
+}
+
+int RVReadMem( void * dev, uint32_t memaddy, uint8_t * payload, int len )
+{
+	if( !MCF.ReadBinaryBlob )
+	{
+		fprintf( stderr, "Error: Can't alter halt mode with this programmer.\n" );
+		exit( -6 );
+	}
+	int ret = MCF.ReadBinaryBlob( dev, memaddy, len, payload );
+	if( ret < 0 )
+	{
+		fprintf( stderr, "Error reading binary blob at %08x\n", memaddy );
+	}
+	return ret;
+}
+
+static int InternalDisableBreakpoint( void * dev, int i )
+{
+	int r;
+	if( software_breakpoint_type[i] == 1 )
+	{
+		//32-bit instruction
+		r = MCF.WriteBinaryBlob( dev, software_breakpoint_addy[i], 4, (uint8_t*)&previous_word_at_breakpoint_address[i] );
+	}
+	else
+	{
+		//16-bit instruction
+		r = MCF.WriteBinaryBlob( dev, software_breakpoint_addy[i], 2, (uint8_t*)&previous_word_at_breakpoint_address[i] );
+	}
+	previous_word_at_breakpoint_address[i] = 0;
+	software_breakpoint_type[i] = 0;
+	software_breakpoint_addy[i] = 0;
+	return r;
+}
+
+int RVHandleBreakpoint( void * dev, int set, uint32_t address )
+{
+	int i;
+	int first_free = -1;
+	for( i = 0; i < MAX_SOFTWARE_BREAKPOINTS; i++ )
+	{
+		if( software_breakpoint_type[i] && software_breakpoint_addy[i] == address )
+			break;
+		if( first_free < 0 && software_breakpoint_type[i] == 0 )
+			first_free = i;
+	}
+
+	if( i != MAX_SOFTWARE_BREAKPOINTS )
+	{
+		// There is already a break slot here.
+		if( !set )
+		{
+			InternalDisableBreakpoint( dev, i );
+		}
+		else
+		{
+			// Already set.
+		}
+	}
+	else
+	{
+		if( first_free == -1 )
+		{
+			fprintf( stderr, "Error: Too many breakpoints\n" );
+			return -1;
+		}
+		if( set )
+		{
+			i = first_free;
+			uint32_t readval_at_addy;
+			int r = MCF.ReadBinaryBlob( dev, address, 4, (uint8_t*)&readval_at_addy );
+			if( r ) return -5;
+			if( ( readval_at_addy & 3 ) == 3 ) // Check opcode LSB's.
+			{
+				// 32-bit instruction.
+				software_breakpoint_type[i] = 1;
+				software_breakpoint_addy[i] = address;
+				previous_word_at_breakpoint_address[i] = readval_at_addy;
+				uint32_t ebreak = 0x00100073; // ebreak
+				MCF.WriteBinaryBlob( dev, address, 4, (uint8_t*)&ebreak );
+			}
+			else
+			{
+				// 16-bit instructions
+				software_breakpoint_type[i] = 2;
+				software_breakpoint_addy[i] = address;
+				previous_word_at_breakpoint_address[i] = readval_at_addy & 0xffff;
+				uint32_t ebreak = 0x9002; // c.ebreak
+				MCF.WriteBinaryBlob( dev, address, 2, (uint8_t*)&ebreak );
+			}
+		}
+		else
+		{
+			// Already unset.
+		}
+	}
+
+	return 0;
+}
+
+int RVWriteRAM(void * dev, uint32_t memaddy, uint32_t length, uint8_t * payload )
+{
+	if( !MCF.WriteBinaryBlob )
+	{
+		fprintf( stderr, "Error: Can't alter halt mode with this programmer.\n" );
+		exit( -6 );
+	}
+
+	int r = MCF.WriteBinaryBlob( dev, memaddy, length, payload );
+
+	return r;
+}
+
+void RVHandleDisconnect( void * dev )
+{
+	MCF.HaltMode( dev, 0 );
+	MCF.SetEnableBreakpoints( dev, 0, 0 );
+
+	int i;
+	for( i = 0; i < MAX_SOFTWARE_BREAKPOINTS; i++ )
+	{
+		if( software_breakpoint_type[i]  )
+		{
+			InternalDisableBreakpoint( dev, i );
+		}
+	}
+
+	if( shadow_running_state == 0 )
+	{
+		RVCommandEpilogue( dev );
+	}
+	MCF.HaltMode( dev, 2 );
+	shadow_running_state = 1;
+}
+
+void RVHandleGDBBreakRequest( void * dev )
+{
+	if( shadow_running_state )
+	{
+		MCF.HaltMode( dev, 0 );
+	}
+}
+
+
+int PollGDBServer( void * dev )
+{
+	return MicroGDBPollServer( dev );
+}
+
+void ExitGDBServer( void * dev )
+{
+	MicroGDBExitServer( dev );
+}
+
+
+int SetupGDBServer( void * dev )
+{
+	return MicroGDBStubStartup( dev );
+}
+
+
diff --git a/minichlink/minichlink.c b/minichlink/minichlink.c
index a384b3721a2855317c1b487768e308d033beecfa..0b44dea162e1e9f4fa3dbbc03898e1588d8b8ebb 100644
--- a/minichlink/minichlink.c
+++ b/minichlink/minichlink.c
@@ -160,24 +160,45 @@ keep_going:
 				else
 					goto unimplemented;
 				break;
+			case 'G':
 			case 'T':
 			{
 				if( !MCF.PollTerminal )
 					goto unimplemented;
+
+				if( argchar[1] == 'G' && SetupGDBServer( dev ) )
+				{
+					printf( "Error: can't start GDB server\n" );
+					return -1;
+				}
+
 				do
 				{
 					uint8_t buffer[256];
-					int r = MCF.PollTerminal( dev, buffer, sizeof( buffer ), 0, 0 );
-					if( r < 0 )
+					if( !IsGDBServerInShadowHaltState( dev ) )
 					{
-						fprintf( stderr, "Terminal dead.  code %d\n", r );
-						return -32;
+						int r = MCF.PollTerminal( dev, buffer, sizeof( buffer ), 0, 0 );
+						if( r < 0 )
+						{
+							fprintf( stderr, "Terminal dead.  code %d\n", r );
+							return -32;
+						}
+						if( r > 0 )
+						{
+							fwrite( buffer, r, 1, stdout ); 
+						}
 					}
-					if( r > 0 )
+
+					if( argchar[1] == 'G' )
 					{
-						fwrite( buffer, r, 1, stdout ); 
+						PollGDBServer( dev );
 					}
 				} while( 1 );
+
+				// Currently unreachable - consider reachable-ing
+				if( argchar[1] == 'G' )
+					ExitGDBServer( dev );
+				break;
 			}
 			case 's':
 			{
@@ -201,7 +222,7 @@ keep_going:
 					goto unimplemented;
 				break;
 			}
-			case 'g':
+			case 'm':
 			{
 				iarg+=1;
 				if( iarg >= argc )
@@ -474,14 +495,16 @@ help:
 	fprintf( stderr, " -D Configure NRST as GPIO\n" );
 	fprintf( stderr, " -d Configure NRST as NRST\n" );
 	fprintf( stderr, " -s [debug register] [value]\n" );
-	fprintf( stderr, " -g [debug register]\n" );
+	fprintf( stderr, " -m [debug register]\n" );
+	fprintf( stderr, " -T Terminal Only\n" );
+	fprintf( stderr, " -G Terminal + GDB\n" );
 //	fprintf( stderr, " -P Enable Read Protection (UNTESTED)\n" );
 //	fprintf( stderr, " -p Disable Read Protection (UNTESTED)\n" );
 	fprintf( stderr, " -w [binary image to write] [address, decimal or 0x, try0x08000000]\n" );
 	fprintf( stderr, " -r [output binary image] [memory address, decimal or 0x, try 0x08000000] [size, decimal or 0x, try 16384]\n" );
 	fprintf( stderr, "   Note: for memory addresses, you can use 'flash' 'launcher' 'bootloader' 'option' 'ram' and say \"ram+0x10\" for instance\n" );
-	fprintf( stderr, "   For filename, you can use - for raw or + for hex.\n" );
-	fprintf( stderr, " -T is a terminal. This MUST be the last argument.  You MUST have resumed or \n" );
+	fprintf( stderr, "   For filename, you can use - for raw (terminal) or + for hex (inline).\n" );
+	fprintf( stderr, " -T is a terminal. This MUST be the last argument. Also, will start a gdbserver.\n" );
 
 	return -1;	
 
@@ -651,7 +674,7 @@ static int InternalUnlockBootloader( void * dev )
 
 
 
-static int DefaultWriteHalfWord( void * dev, uint32_t address_to_write, uint32_t data )
+static int DefaultWriteHalfWord( void * dev, uint32_t address_to_write, uint16_t data )
 {
 	int ret = 0;
 	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
@@ -677,7 +700,7 @@ static int DefaultWriteHalfWord( void * dev, uint32_t address_to_write, uint32_t
 	return ret;
 }
 
-static int DefaultReadHalfWord( void * dev, uint32_t address_to_write, uint32_t * data )
+static int DefaultReadHalfWord( void * dev, uint32_t address_to_write, uint16_t * data )
 {
 	int ret = 0;
 	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
@@ -688,7 +711,7 @@ static int DefaultReadHalfWord( void * dev, uint32_t address_to_write, uint32_t
 
 	// Different address, so we don't need to re-write all the program regs.
 	// lh x8,0(x9)  // Write to the address.
-	MCF.WriteReg32( dev, DMPROGBUF0, 0x00049403 );
+	MCF.WriteReg32( dev, DMPROGBUF0, 0x00049403 ); // lh x8, 0(x9)
 	MCF.WriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak
 
 	MCF.WriteReg32( dev, DMDATA0, address_to_write );
@@ -699,8 +722,64 @@ static int DefaultReadHalfWord( void * dev, uint32_t address_to_write, uint32_t
 	ret |= MCF.WaitForDoneOp( dev );
 	iss->currentstateval = -1;
 
+	uint32_t rr;
+	ret |= MCF.ReadReg32( dev, DMDATA0, &rr );
+	*data = rr;
+	return ret;
+}
+
+
+
+static int DefaultWriteByte( void * dev, uint32_t address_to_write, uint8_t data )
+{
+	int ret = 0;
+	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+	if( MCF.VoidHighLevelState ) MCF.VoidHighLevelState( dev );
+	iss->statetag = STTAG( "XXXX" );
+
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
 
-	return ret | MCF.ReadReg32( dev, DMDATA0, data );
+	// Different address, so we don't need to re-write all the program regs.
+	// sh x8,0(x9)  // Write to the address.
+	MCF.WriteReg32( dev, DMPROGBUF0, 0x00848023 ); // sb x8, 0(x9)
+	MCF.WriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak
+
+	MCF.WriteReg32( dev, DMDATA0, address_to_write );
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00231009 ); // Copy data to x9
+	MCF.WriteReg32( dev, DMDATA0, data );
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00271008 ); // Copy data to x8, and execute program.
+
+	ret |= MCF.WaitForDoneOp( dev );
+	iss->currentstateval = -1;
+	return ret;
+}
+
+static int DefaultReadByte( void * dev, uint32_t address_to_write, uint8_t * data )
+{
+	int ret = 0;
+	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+	if( MCF.VoidHighLevelState ) MCF.VoidHighLevelState( dev );
+	iss->statetag = STTAG( "XXXX" );
+
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
+
+	// Different address, so we don't need to re-write all the program regs.
+	// lb x8,0(x9)  // Write to the address.
+	MCF.WriteReg32( dev, DMPROGBUF0, 0x00048403 ); // lb x8, 0(x9)
+	MCF.WriteReg32( dev, DMPROGBUF1, 0x00100073 ); // c.ebreak
+
+	MCF.WriteReg32( dev, DMDATA0, address_to_write );
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00231009 ); // Copy data to x9
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00241000 ); // Only execute.
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00221008 ); // Read x8 into DATA0.
+
+	ret |= MCF.WaitForDoneOp( dev );
+	iss->currentstateval = -1;
+
+	uint32_t rr;
+	ret |= MCF.ReadReg32( dev, DMDATA0, &rr );
+	*data = rr;
+	return ret;
 }
 
 
@@ -812,7 +891,11 @@ int DefaultWriteBinaryBlob( void * dev, uint32_t address_to_write, uint32_t blob
 	if( (address_to_write & 0xff000000) == 0x08000000 || (address_to_write & 0xff000000) == 0x00000000 || (address_to_write & 0x1FFFF800) == 0x1FFFF000 ) 
 		is_flash = 1;
 
-	if( is_flash && MCF.BlockWrite64 && ( address_to_write & 0x3f ) == 0 )
+	// We can't write into flash when mapped to 0x00000000
+	if( is_flash )
+		address_to_write |= 0x08000000;
+
+	if( is_flash && MCF.BlockWrite64 && ( address_to_write & 0x3f ) == 0 && ( blob_size & 0x3f ) == 0 )
 	{
 		int i;
 		for( i = 0; i < blob_size; i+= 64 )
@@ -827,76 +910,137 @@ int DefaultWriteBinaryBlob( void * dev, uint32_t address_to_write, uint32_t blob
 		return 0;
 	}
 
-	if( is_flash ) 
+	if( is_flash && !iss->flash_unlocked )
 	{
-		// Need to unlock flash.
-		// Flash reg base = 0x40022000,
-		// FLASH_MODEKEYR => 0x40022024
-		// FLASH_KEYR => 0x40022004
-
-		if( !iss->flash_unlocked )
-		{
-			if( ( rw = StaticUnlockFlash( dev, iss ) ) )
-				return rw;
-		}
-
-		is_flash = 1;
-
-		printf( "Erasing TO %08x %08x\n", address_to_write, blob_size );
-		MCF.Erase( dev, address_to_write, blob_size, 0 );
+		if( ( rw = StaticUnlockFlash( dev, iss ) ) )
+			return rw;
 	}
-	printf( "Done\n" );
-	MCF.FlushLLCommands( dev );
-	MCF.DelayUS( dev, 100 ); // Why do we need this?
 
-	uint32_t wp = address_to_write;
-	uint32_t ew = wp + blob_size;
-	int group = -1;
 
-	while( wp < ew )
+	uint8_t tempblock[64];
+	int sblock =  address_to_write >> 6;
+	int eblock = ( address_to_write + blob_size + 0x3f) >> 6;
+	int b;
+	int rsofar = 0;
+
+	for( b = sblock; b < eblock; b++ )
 	{
-		if( is_flash )
-		{
-			group = (wp & 0xffffffc0);
-			MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG ); // THIS IS REQUIRED, (intptr_t)&FLASH->CTLR = 0x40022010
-			MCF.WriteWord( dev, 0x40022010, CR_BUF_RST | CR_PAGE_PG );  // (intptr_t)&FLASH->CTLR = 0x40022010
+		int offset_in_block = address_to_write - (b * 64);
+		if( offset_in_block < 0 ) offset_in_block = 0;
+		int end_o_plus_one_in_block = ( address_to_write + blob_size ) - (b*64);
+		if( end_o_plus_one_in_block > 64 ) end_o_plus_one_in_block = 64;
+		int	base = b * 64;
 
-			int j;
-			for( j = 0; j < 16; j++ )
+		if( offset_in_block == 0 && end_o_plus_one_in_block == 64 )
+		{
+			if( MCF.BlockWrite64 ) 
+			{
+				int r = MCF.BlockWrite64( dev, base, blob + rsofar );
+				rsofar += 64;
+				if( r )
+				{
+					fprintf( stderr, "Error writing block at memory %08x\n", base );
+					return r;
+				}
+			}
+			else 					// Block Write not avaialble
 			{
-				int index = (wp-address_to_write);
-				uint32_t data = 0xffffffff;
-				if( index + 3 < blob_size )
-					data = ((uint32_t*)blob)[index/4];
-				else if( (int32_t)(blob_size - index) > 0 )
+				if( is_flash )
+				{
+					MCF.Erase( dev, base, 64, 0 );
+					MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG ); // THIS IS REQUIRED, (intptr_t)&FLASH->CTLR = 0x40022010
+					MCF.WriteWord( dev, 0x40022010, CR_BUF_RST | CR_PAGE_PG );  // (intptr_t)&FLASH->CTLR = 0x40022010
+				}
+
+				int j;
+				for( j = 0; j < 16; j++ )
+				{
+					uint32_t writeword;
+					memcpy( &writeword, blob + rsofar, 4 );
+					MCF.WriteWord( dev, j*4+base, writeword );
+					rsofar += 4;
+				}
+
+				if( is_flash )
 				{
-					printf( "%d %d\n", blob_size, index );
-					memcpy( &data, &blob[index], blob_size - index );
+					MCF.WriteWord( dev, 0x40022014, base );  //0x40022014 -> FLASH->ADDR
+					MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG|CR_STRT_Set ); // 0x40022010 -> FLASH->CTLR
+					if( MCF.WaitForFlash ) MCF.WaitForFlash( dev );
 				}
-				MCF.WriteWord( dev, wp, data );
-				wp += 4;
 			}
-			MCF.WriteWord( dev, 0x40022014, group );  //0x40022014 -> FLASH->ADDR
-			MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG|CR_STRT_Set ); // 0x40022010 -> FLASH->CTLR
-			if( MCF.WaitForFlash ) MCF.WaitForFlash( dev );
 		}
 		else
 		{
-			int index = (wp-address_to_write);
-			uint32_t data = 0xffffffff;
-			if( index + 3 < blob_size )
-				data = ((uint32_t*)blob)[index/4];
-			else if( (int32_t)(blob_size - index) > 0 )
-				memcpy( &data, &blob[index], blob_size - index );
-			MCF.WriteWord( dev, wp, data );
-			wp += 4;
+			//Ok, we have to do something wacky.
+			if( is_flash )
+			{
+				MCF.ReadBinaryBlob( dev, base, 64, tempblock );
+				MCF.Erase( dev, base, 64, 0 );
+				MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG ); // THIS IS REQUIRED, (intptr_t)&FLASH->CTLR = 0x40022010
+				MCF.WriteWord( dev, 0x40022010, CR_BUF_RST | CR_PAGE_PG );  // (intptr_t)&FLASH->CTLR = 0x40022010
+
+				// Permute tempblock
+				int tocopy = end_o_plus_one_in_block - offset_in_block;
+				memcpy( tempblock + offset_in_block, blob + rsofar, tocopy );
+				rsofar += tocopy;
+
+				int j;
+				for( j = 0; j < 16; j++ )
+				{
+					MCF.WriteWord( dev, j*4+base, *(uint32_t*)(tempblock + j * 4) );
+					rsofar += 4;
+				}
+				MCF.WriteWord( dev, 0x40022014, base );  //0x40022014 -> FLASH->ADDR
+				MCF.WriteWord( dev, 0x40022010, CR_PAGE_PG|CR_STRT_Set ); // 0x40022010 -> FLASH->CTLR
+				if( MCF.WaitForFlash && MCF.WaitForFlash( dev ) ) goto timedout;
+			}
+			else
+			{
+				// Accessing RAM.  Be careful to only do the needed operations.
+				int j;
+				for( j = 0; j < 16; j++ )
+				{
+					uint32_t taddy = j*4;
+					if( offset_in_block <= taddy && end_o_plus_one_in_block >= taddy + 4 )
+					{
+						MCF.WriteWord( dev, taddy + base, *(uint32_t*)(blob + rsofar) );
+						rsofar += 4;
+					}
+					else if( ( offset_in_block & 1 ) || ( end_o_plus_one_in_block & 1 ) )
+					{
+						// Bytes only.
+						int j;
+						for( j = 0; j < 4; j++ )
+						{
+							if( taddy >= offset_in_block && taddy < end_o_plus_one_in_block )
+							{
+								MCF.WriteByte( dev, taddy + base, *(uint32_t*)(blob + rsofar) );
+								rsofar ++;
+							}
+							taddy++;
+						}
+					}
+					else
+					{
+						// Half-words
+						int j;
+						for( j = 0; j < 4; j+=2 )
+						{
+							if( taddy >= offset_in_block && taddy < end_o_plus_one_in_block )
+							{
+								MCF.WriteHalfWord( dev, taddy + base, *(uint32_t*)(blob + rsofar) );
+								rsofar +=2;
+							}
+							taddy+=2;
+						}
+					}
+				}
+			}
 		}
 	}
 
-	if( is_flash )
-	{
-		if( MCF.WaitForFlash && MCF.WaitForFlash( dev ) ) goto timedout;
-	}
+	MCF.FlushLLCommands( dev );
+	MCF.DelayUS( dev, 100 ); // Why do we need this?
 	return 0;
 timedout:
 	fprintf( stderr, "Timed out\n" );
@@ -906,17 +1050,34 @@ timedout:
 static int DefaultReadWord( void * dev, uint32_t address_to_read, uint32_t * data )
 {
 	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
-	if( iss->statetag != STTAG( "RDSQ" ) || address_to_read != iss->currentstateval )
+
+	int autoincrement = 1;
+	if( address_to_read == 0x40022010 || address_to_read == 0x4002200C )  // Don't autoincrement when checking flash flag. 
+		autoincrement = 0;
+
+	if( iss->statetag != STTAG( "RDSQ" ) || address_to_read != iss->currentstateval || autoincrement != iss->autoincrement)
 	{
 		if( iss->statetag != STTAG( "RDSQ" ) )
 		{
 			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0 ); // Disable Autoexec.
+
 			// c.lw x8,0(x11) // Pull the address from DATA1
 			// c.lw x9,0(x8)  // Read the data at that location.
 			MCF.WriteReg32( dev, DMPROGBUF0, 0x40044180 );
-			// c.addi x8, 4
-			// c.sw x9, 0(x10) // Write back to DATA0
-			MCF.WriteReg32( dev, DMPROGBUF1, 0xc1040411 );
+			if( autoincrement )
+			{
+				// c.addi x8, 4
+				// c.sw x9, 0(x10) // Write back to DATA0
+
+				MCF.WriteReg32( dev, DMPROGBUF1, 0xc1040411 );
+			}
+			else
+			{
+				// c.nop
+				// c.sw x9, 0(x10) // Write back to DATA0
+
+				MCF.WriteReg32( dev, DMPROGBUF1, 0xc1040001 );
+			}
 			// c.sw x8, 0(x11) // Write addy to DATA1
 			// c.ebreak
 			MCF.WriteReg32( dev, DMPROGBUF2, 0x9002c180 );
@@ -925,7 +1086,8 @@ static int DefaultReadWord( void * dev, uint32_t address_to_read, uint32_t * dat
 			{
 				StaticUpdatePROGBUFRegs( dev );
 			}
-			MCF.WriteReg32( dev, DMABSTRACTAUTO, 1 ); // Enable Autoexec.
+			MCF.WriteReg32( dev, DMABSTRACTAUTO, 1 ); // Enable Autoexec (different kind of autoinc than outer autoinc)
+			iss->autoincrement = autoincrement;
 		}
 
 		MCF.WriteReg32( dev, DMDATA1, address_to_read );
@@ -937,9 +1099,11 @@ static int DefaultReadWord( void * dev, uint32_t address_to_read, uint32_t * dat
 		MCF.WaitForDoneOp( dev );
 	}
 
-	iss->currentstateval += 4;
+	if( iss->autoincrement )
+		iss->currentstateval += 4;
 
-	return MCF.ReadReg32( dev, DMDATA0, data );
+	int r = MCF.ReadReg32( dev, DMDATA0, data );
+	return r;
 }
 
 static int StaticUnlockFlash( void * dev, struct InternalState * iss )
@@ -1017,20 +1181,157 @@ int DefaultReadBinaryBlob( void * dev, uint32_t address_to_read_from, uint32_t r
 {
 	uint32_t rpos = address_to_read_from;
 	uint32_t rend = address_to_read_from + read_size;
+
 	while( rpos < rend )
 	{
-		uint32_t rw;
-		int r = DefaultReadWord( dev, rpos, &rw );
-		if( r ) return r;
+		int r;
 		int remain = rend - rpos;
-		if( remain > 3 ) remain = 4;
-		memcpy( blob, &rw, remain );
-		blob += 4;
-		rpos += 4;
+
+		if( ( rpos & 3 ) == 0 && remain >= 4 )
+		{
+			uint32_t rw;
+			r = MCF.ReadWord( dev, rpos, &rw );
+			if( r ) return r;
+			memcpy( blob, &rw, remain );
+			blob += 4;
+			rpos += 4;
+		}
+		else
+		{
+			if( ( rpos & 1 ) )
+			{
+				uint8_t rw;
+				r = MCF.ReadByte( dev, rpos, &rw );
+				if( r ) return r;
+				memcpy( blob, &rw, 1 );
+				blob += 1;
+				rpos += 1;
+				remain -= 1;
+			}
+			if( ( rpos & 2 ) && remain >= 2 )
+			{
+				uint16_t rw;
+				r = MCF.ReadHalfWord( dev, rpos, &rw );
+				if( r ) return r;
+				memcpy( blob, &rw, 2 );
+				blob += 2;
+				rpos += 2;
+				remain -= 2;
+			}
+			if( remain >= 1 )
+			{
+				uint8_t rw;
+				r = MCF.ReadByte( dev, rpos, &rw );
+				if( r ) return r;
+				memcpy( blob, &rw, 1 );
+				blob += 1;
+				rpos += 1;
+				remain -= 1;
+			}
+		}
 	}
 	return 0;
 }
 
+int DefaultReadCPURegister( void * dev, uint32_t regno, uint32_t * regret )
+{
+	if( !MCF.WriteReg32 || !MCF.ReadReg32 )
+	{
+		fprintf( stderr, "Error: Can't read CPU register on this programmer because it is missing read/writereg32\n" );
+		return -5;
+	}
+
+	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
+	iss->statetag = STTAG( "REGR" );
+
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00220000 | regno ); // Read xN into DATA0.
+	int r = MCF.ReadReg32( dev, DMDATA0, regret );
+
+	return r;
+}
+
+int DefaultReadAllCPURegisters( void * dev, uint32_t * regret )
+{
+	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000001 ); // Disable Autoexec.
+	iss->statetag = STTAG( "RER2" );
+	int i;
+	for( i = 0; i < 16; i++ )
+	{
+		MCF.WriteReg32( dev, DMCOMMAND, 0x00220000 | 0x1000 | i ); // Read xN into DATA0.
+		if( MCF.ReadReg32( dev, DMDATA0, regret + i ) )
+		{
+			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
+			return -5;
+		}
+	}
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00220000 | 0x7b1 ); // Read xN into DATA0.
+	int r = MCF.ReadReg32( dev, DMDATA0, regret + i );
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
+	return r;
+}
+
+int DefaultWriteAllCPURegisters( void * dev, uint32_t * regret )
+{
+	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000001 ); // Disable Autoexec.
+	iss->statetag = STTAG( "WER2" );
+	int i;
+	for( i = 0; i < 16; i++ )
+	{
+		MCF.WriteReg32( dev, DMCOMMAND, 0x00230000 | 0x1000 | i ); // Read xN into DATA0.
+		if( MCF.WriteReg32( dev, DMDATA0, regret[i] ) )
+		{
+			MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
+			return -5;
+		}
+	}
+	MCF.WriteReg32( dev, DMCOMMAND, 0x00230000 | 0x7b1 ); // Read xN into DATA0.
+	int r = MCF.WriteReg32( dev, DMDATA0, regret[i] );
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
+	return r;
+}
+
+
+int DefaultWriteCPURegister( void * dev, uint32_t regno, uint32_t value )
+{
+	if( !MCF.WriteReg32 || !MCF.ReadReg32 )
+	{
+		fprintf( stderr, "Error: Can't read CPU register on this programmer because it is missing read/writereg32\n" );
+		return -5;
+	}
+
+	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+	MCF.WriteReg32( dev, DMABSTRACTAUTO, 0x00000000 ); // Disable Autoexec.
+	iss->statetag = STTAG( "REGW" );
+	MCF.WriteReg32( dev, DMDATA0, value );
+	return MCF.WriteReg32( dev, DMCOMMAND, 0x00230000 | regno ); // Write xN from DATA0.
+}
+
+int DefaultSetEnableBreakpoints( void * dev, int is_enabled, int single_step )
+{
+	if( !MCF.ReadCPURegister || !MCF.WriteCPURegister )
+	{
+		fprintf( stderr, "Error: Can't set breakpoints on this programmer because it is missing read/writereg32\n" );
+		return -5;
+	}
+	uint32_t DCSR;
+	if( MCF.ReadCPURegister( dev, 0x7b0, &DCSR ) )
+		fprintf( stderr, "Error: DCSR could not be read\n" );
+	DCSR |= 0xb600;
+	if( single_step )
+		DCSR |= 4;
+	else
+		DCSR &=~4;
+
+	//printf( "Setting DCSR: %08x\n", DCSR );
+	if( MCF.WriteCPURegister( dev, 0x7b0, DCSR ) )
+		fprintf( stderr, "Error: DCSR could not be read\n" );
+
+	return 0;
+}
+
 
 static int DefaultHaltMode( void * dev, int mode )
 {
@@ -1342,6 +1643,13 @@ fail:
 	return -11;
 }
 
+int DefaultVoidHighLevelState( void * dev )
+{
+	struct InternalState * iss = (struct InternalState*)(((struct ProgrammerStructBase*)dev)->internal);
+	iss->statetag = STTAG( "VOID" );
+	return 0;
+}
+
 int SetupAutomaticHighLevelFunctions( void * dev )
 {
 	// Will populate high-level functions from low-level functions.
@@ -1360,10 +1668,24 @@ int SetupAutomaticHighLevelFunctions( void * dev )
 		MCF.WriteWord = DefaultWriteWord;
 	if( !MCF.WriteHalfWord )
 		MCF.WriteHalfWord = DefaultWriteHalfWord;
+	if( !MCF.WriteByte )
+		MCF.WriteByte = DefaultWriteByte;
+	if( !MCF.ReadCPURegister )
+		MCF.ReadCPURegister = DefaultReadCPURegister;
+	if( !MCF.WriteCPURegister )
+		MCF.WriteCPURegister = DefaultWriteCPURegister;
+	if( !MCF.WriteAllCPURegisters )
+		MCF.WriteAllCPURegisters = DefaultWriteAllCPURegisters;
+	if( !MCF.ReadAllCPURegisters )
+		MCF.ReadAllCPURegisters = DefaultReadAllCPURegisters;
+	if( !MCF.SetEnableBreakpoints )
+		MCF.SetEnableBreakpoints = DefaultSetEnableBreakpoints;
 	if( !MCF.ReadWord )
 		MCF.ReadWord = DefaultReadWord;
 	if( !MCF.ReadHalfWord )
 		MCF.ReadHalfWord = DefaultReadHalfWord;
+	if( !MCF.ReadByte )
+		MCF.ReadByte = DefaultReadByte;
 	if( !MCF.Erase )
 		MCF.Erase = DefaultErase;
 	if( !MCF.HaltMode )
@@ -1380,10 +1702,13 @@ int SetupAutomaticHighLevelFunctions( void * dev )
 		MCF.Unbrick = DefaultUnbrick;
 	if( !MCF.ConfigureNRSTAsGPIO )
 		MCF.ConfigureNRSTAsGPIO = DefaultConfigureNRSTAsGPIO;
+	if( !MCF.VoidHighLevelState )
+		MCF.VoidHighLevelState = DefaultVoidHighLevelState;
 
 	struct InternalState * iss = malloc( sizeof( struct InternalState ) );
 	iss->statetag = 0;
 	iss->currentstateval = 0;
+	iss->autoincrement = 0;
 
 	((struct ProgrammerStructBase*)dev)->internal = iss;
 	return 0;
diff --git a/minichlink/minichlink.h b/minichlink/minichlink.h
index 9a0e1e64a66479a0b405c6603c16e290c8865af4..008e8f8e5650331d6abdf66a8dd908eef8b46718 100644
--- a/minichlink/minichlink.h
+++ b/minichlink/minichlink.h
@@ -24,10 +24,10 @@ struct MiniChlinkFunctions
 	int (*HaltMode)( void * dev, int mode ); //0 for halt, 1 for reset, 2 for resume
 	int (*ConfigureNRSTAsGPIO)( void * dev, int one_if_yes_gpio );
 
-	// WARNING: Reading/writing must be at 32-bit boundaries for 32-bit sizes.
-	// WARNING: Writing binary blobs may write groups of 64-bytes.
+	// No boundary or limit rules.  Must support any combination of alignment and size.
 	int (*WriteBinaryBlob)( void * dev, uint32_t address_to_write, uint32_t blob_size, uint8_t * blob );
 	int (*ReadBinaryBlob)( void * dev, uint32_t address_to_read_from, uint32_t read_size, uint8_t * blob );
+
 	int (*Erase)( void * dev, uint32_t address, uint32_t length, int type ); //type = 0 for fast, 1 for whole-chip
 
 	// MUST be 4-byte-aligned.
@@ -35,6 +35,20 @@ struct MiniChlinkFunctions
 	int (*WriteWord)( void * dev, uint32_t address_to_write, uint32_t data );
 	int (*ReadWord)( void * dev, uint32_t address_to_read, uint32_t * data );
 
+	// Debugging operations.
+	//  Note: You must already be in break mode to use these otherwise they
+	//  will return nonsensical data.
+	// For x0...xN, use 0x1000 + regno.
+	// For PC, use 0x7b1
+	int (*ReadCPURegister)( void * dev, uint32_t regno, uint32_t * regret );
+	int (*WriteCPURegister)( void * dev, uint32_t regno, uint32_t regval );
+
+	// Actually returns 17 registers (All 16 CPU registers + the debug register)
+	int (*ReadAllCPURegisters)( void * dev, uint32_t * regret );
+	int (*WriteAllCPURegisters)( void * dev, uint32_t * regret );
+
+	int (*SetEnableBreakpoints)( void * dev, int halt_on_break, int single_step );
+
 	int (*WaitForFlash)( void * dev );
 	int (*WaitForDoneOp)( void * dev );
 
@@ -57,9 +71,12 @@ struct MiniChlinkFunctions
 
 	int (*VendorCommand)( void * dev, const char * command );
 
-	// Do Not override these.  they are cursed.
-	int (*WriteHalfWord)( void * dev, uint32_t address_to_write, uint32_t data );
-	int (*ReadHalfWord)( void * dev, uint32_t address_to_read, uint32_t * data );
+	// Probably no need to override these.  The base layer handles them.
+	int (*WriteHalfWord)( void * dev, uint32_t address_to_write, uint16_t data );
+	int (*ReadHalfWord)( void * dev, uint32_t address_to_read, uint16_t * data );
+
+	int (*WriteByte)( void * dev, uint32_t address_to_write, uint8_t data );
+	int (*ReadByte)( void * dev, uint32_t address_to_read, uint8_t * data );
 };
 
 /** If you are writing a driver, the minimal number of functions you can implement are:
@@ -86,6 +103,7 @@ struct InternalState
 	uint32_t flash_unlocked;
 	int lastwriteflags;
 	int processor_in_mode;
+	int autoincrement;
 };
 
 
@@ -123,5 +141,14 @@ int SetupAutomaticHighLevelFunctions( void * dev );
 // Useful for converting numbers like 0x, etc.
 int64_t SimpleReadNumberInt( const char * number, int64_t defaultNumber );
 
+// For drivers to call
+int DefaultVoidHighLevelState( void * dev );
+
+// GDBSever Functions
+int SetupGDBServer( void * dev );
+int PollGDBServer( void * dev );
+int IsGDBServerInShadowHaltState( void * dev );
+void ExitGDBServer( void * dev );
+
 #endif
 
diff --git a/minichlink/pgm-esp32s2-ch32xx.c b/minichlink/pgm-esp32s2-ch32xx.c
index 212f66f51f83e92089231caefc96deb8b7ce335d..27869ff4f479ed067c6f3eae814bca0c1e64c4f4 100644
--- a/minichlink/pgm-esp32s2-ch32xx.c
+++ b/minichlink/pgm-esp32s2-ch32xx.c
@@ -252,6 +252,7 @@ int ESPVoidHighLevelState( void * dev )
 	struct ESP32ProgrammerStruct * eps = (struct ESP32ProgrammerStruct *)dev;
 	Write2LE( eps, 0x05fe );
 	ESPFlushLLCommands( dev );	
+	DefaultVoidHighLevelState( dev );
 	return 0;
 }