Patching functions and libraries
.
Introduction
After this forum post culminating in a very nicely pitched wish-list item from Robbie King, I decided to take on the challenge of working out how to deliver "Robbies Dream".
The dream is to be able to make low cost LPC11U24 hardware that doesn't even have a crystal. It's possible in theory, but the mbed libraries are hard wired to use the on-board crystal. While it is possible to switch to the internal oscillator in software from user code, the LPC11U24 wont get as far as the user code without the crystal.
As this was clearly going to be a software exercise, I was hoping that Emilio or Samuel would step in and make it all work beautifully. Unfortunately they are both really busy on some super-cool stuff, and so I quote (literally) :
wrote:
"i think it should be chris' challenge to make it work :)"
The nub of the problem
As you're all aware, the mbed libraries are currently (as of 20/9/2012) closed source. If they were open, it would be possible to go into the clock set-up code that executes before main() in reached and simply set up the clocks as you please. Its fiddly work, lots of documentation to read, and lots of mistakes that are all to easy to make. I have made a few of them while doing this, and have made use of my own How to unbrick and mbed page! :-)
So, without the ability to modify the source, we need some other way to override the settings. Fortunately Emilio came to the the rescue, and introduced me to $Sub$$ and $Super$$.
At this point, it would be beneficial to have a look at Dr Rob Touslons course notes, especially the "Modular Programming" section. Understanding the process of how a program gets compiled and linked into the final binary will make everything that follows a little clearer.
$Sub$$ and $Super$$ to the rescue
These two features of the ARM compiler allow the user to patch symbols defined in one compilation unit from another. This is most often used if you are trying to patch the functionality of a functions in compiled code that you don't have the source to. They were a clear choice of how to deliver Robbies Dream, as all we really need to do is patch over the clock set up code.
The offical ARM documentation for these features are can be found here.
I often find it difficult to take abstract examples and translate them to my own use, so here is a worked example.
I have a simple function that takes an integer. It creates a DigitalOut object on LED1, and sets it on/off according to the integer passed in.
function.cpp
#include "function.h" #include "mbed.h" void function (int i) { DigitalOut led(LED1); led=i; }
It is used in a simple program that simply turns the LED on and off again - Blinky!
main.cpp
#include "mbed.h" #include "function.h" int main() { while(1) { function(1); wait(0.5); function(0); wait(0.5); } }
Using $Sub$$
Now imagine that function() is buried away in a library that I only have object for, but I want to change its functionality, this is exactly what $sub$$ is designed for.
I can now write a new function called void $Sub$$function(int) and give it alternate behaviour. When the final binary is linked, any call to function(int), becomes a call to my new function "$Sub$$function(int)"
main.cpp
#include "mbed.h" #include "function.h" void $Sub$$function (int i) { DigitalOut led(LED3); led=i; } int main() { while(1) { function(1); wait(0.5); function(0); wait(0.5); } }
This is great, as I have now been able to replace one function with another. However, the story gets even better when we consider what $Super$$ can do for us
Using $Super$$
In the previous example, I have simply replaced one function with another. However, I can still get access to the original definition of the function(int) and use it. An example might be is a function requried some additional setup before it was called, or cleanup after it was called.
So in my $Sub$$ only version I simply replaced blinky on LED1 with blinky on LED3. What if I wanted to do both, with something in between?
Import program
00001 #include "mbed.h" 00002 #include "function.h" 00003 00004 extern void $Super$$function(int i); 00005 00006 void $Sub$$function (int i) { 00007 DigitalOut led(LED3); 00008 led=i; 00009 wait(0.1); 00010 $Super$$function(i); 00011 } 00012 00013 int main() { 00014 00015 while(1) { 00016 function(1); 00017 wait(0.5); 00018 function(0); 00019 wait(0.5); 00020 } 00021 }
In this example "extern void $Super$$function(int i);" is a declaration that assures the compiler that a symbol called "$Super$$function" will have been compiled elsewhere and that it will all be resolved by the linker. In fact this will come to exist when $Sub$$ is used, and this renames the original "function(int)" to "$Super$$function(int)"
This example program also now makes a call to the original version of function(int) after it has executed its own commands.
The result of all this is that the contents of main() have remained unchanged, but we have gone from blinking LED1 to blinking LED3, to blinking both with a time delay, all by substituting function symbols.
Back to the Crystal issue...
So now we have a way to patch code that we don't have access to, let's have a look at how to deliver Robbies Dream.
Nosing through the (closed) source, I found a file call "system_LPC11Uxx.c", which contains a function called "void SystemInit(void)"
This does look exactly like the function that I need to patch - it is full of references to clock source settings, PLL configurations and other related things.
At this point, I have a look at the user manual for the LPC11U24
Chapter 3 is entited the System control block, and contains all the information I'll be needing. Figure 7. is the most useful diagram, as it show a nice schematic of how the clocks are generated and routed.
After some head scratching, note making and experimentation, I arrive at the conclusion of what needs to be done :
- Patch SystemInit() - Remove all conditional compilation and force the settings I want
- Don't power up the System Oscillator (a good experiment to ensure we're not still using it!)
- Select the SYSTEMPLL input as "0x0" - The Internal RC oscillator
- Ensure the MAINCLK is selecte as SYSTEMPLL output
- Select the USBPLLCLK input as "0x0" - The internal RC oscillator - This is only good for low speed USB
And here it is, my implementation the LPC11U24 running blinking with no external crystal
Import program
00001 #include "mbed.h" 00002 00003 DigitalOut myled(LED1); 00004 00005 extern int stdio_retargeting_module; 00006 00007 /** 00008 * Initialize the system 00009 * 00010 * @param none 00011 * @return none 00012 * 00013 * @brief Setup the microcontroller system. 00014 * Initialize the System. 00015 */ 00016 extern "C" void $Sub$$SystemInit (void) 00017 { 00018 00019 // select the PLL input 00020 LPC_SYSCON->SYSPLLCLKSEL = 0x0; // Select PLL Input source 0=IRC, 1=OSC 00021 LPC_SYSCON->SYSPLLCLKUEN = 0x01; /* Update Clock Source */ 00022 LPC_SYSCON->SYSPLLCLKUEN = 0x00; /* Toggle Update Register */ 00023 LPC_SYSCON->SYSPLLCLKUEN = 0x01; 00024 while (!(LPC_SYSCON->SYSPLLCLKUEN & 0x01)); /* Wait Until Updated */ 00025 00026 // Power up the system PLL 00027 LPC_SYSCON->SYSPLLCTRL = 0x00000023; 00028 LPC_SYSCON->PDRUNCFG &= ~(1 << 7); /* Power-up SYSPLL */ 00029 while (!(LPC_SYSCON->SYSPLLSTAT & 0x01)); /* Wait Until PLL Locked */ 00030 00031 // Select the main clock source 00032 LPC_SYSCON->MAINCLKSEL = 0x3; // Select main Clock source, 0=IRC, 1=PLLin, 2=WDO, 3=PLLout 00033 LPC_SYSCON->MAINCLKUEN = 0x01; /* Update MCLK Clock Source */ 00034 LPC_SYSCON->MAINCLKUEN = 0x00; /* Toggle Update Register */ 00035 LPC_SYSCON->MAINCLKUEN = 0x01; 00036 while (!(LPC_SYSCON->MAINCLKUEN & 0x01)); /* Wait Until Updated */ 00037 00038 LPC_SYSCON->SYSAHBCLKDIV = 0x00000001; 00039 00040 LPC_SYSCON->PDRUNCFG &= ~(1 << 10); /* Power-up USB PHY */ 00041 LPC_SYSCON->PDRUNCFG &= ~(1 << 8); /* Power-up USB PLL */ 00042 LPC_SYSCON->USBPLLCLKSEL = 0x0; // 0=IRC, 1=System clock, only good for low speed 00043 LPC_SYSCON->USBPLLCLKUEN = 0x01; /* Update Clock Source */ 00044 LPC_SYSCON->USBPLLCLKUEN = 0x00; /* Toggle Update Register */ 00045 LPC_SYSCON->USBPLLCLKUEN = 0x01; 00046 00047 while (!(LPC_SYSCON->USBPLLCLKUEN & 0x01)); /* Wait Until Updated */ 00048 LPC_SYSCON->USBPLLCTRL = 0x00000023; 00049 00050 while (!(LPC_SYSCON->USBPLLSTAT & 0x01)); /* Wait Until PLL Locked */ 00051 LPC_SYSCON->USBCLKSEL = 0x00; /* Select USB PLL */ 00052 00053 LPC_SYSCON->USBCLKSEL = 0x00000000; /* Select USB Clock */ 00054 LPC_SYSCON->USBCLKDIV = 0x00000001; /* Set USB clock divider */ 00055 00056 /* System clock to the IOCON needs to be enabled or 00057 most of the I/O related peripherals won't work. */ 00058 LPC_SYSCON->SYSAHBCLKCTRL |= (1<<16); 00059 stdio_retargeting_module = 1; 00060 00061 } 00062 00063 00064 int main() 00065 { 00066 while(1) { 00067 myled = 1; 00068 wait(0.25); 00069 myled = 0; 00070 wait(0.25); 00071 } 00072 }
Note the missing Crystal, half way up onthe right by p26/p27, savagely removed with a screwdriver!
Conclusion
As s result of this, we now have two new weapons up our sleeve, $Sub$$ and $Super$$, and we've seen how with a little insight we can still patch our way around things that are hidden along with the source code.
I hope that it also delivers Robbies Dream :-)
5 comments on Patching functions and libraries:
Please log in to post comments.
WOW, HUGE THANKS to Chris and @mbed team for showing us how it's done (and for making my dream a reality)! The $Sub$$ and $Super$$ features will undoubtedly be very useful to other people who want to hack away at the mbed libraries... until they become open-source of course (wink, wink)!
Many Cheers from Robbie's corner!