User Tools

Site Tools


haxx:projects:sbc:rkdeveloptool

rkdeveloptool: a build experience

Overview

In my playings with the RK3328 renegade SBCs, one of my boards that was previously working seemingly out of the blue become unresponsive. Doesn't seem to boot (despite confirming the SD card is okay, and boots successfully on another board; and another known working card still fails to evoke any change in the troubled board). Also, attempts to communicate over the serial console go unanswered.

Seems there is a special maskrom recovery mode on these boards, enabling a lower level interaction, in the event we've accidentally messed up writing the firmware (otherwise “bricking” the board).

RockChip has a tool for interacting with their boards, rkdeveloptool, found here:

Described as “rkdeveloptool gives you a simple way to read/write rockusb device.let's start.”

Let's do this!

Premise

I find a big hurdle many encounter in accomplishing pursuits is their thought process. They go in not expecting anything to fail, and when they encounter problems, are overwhelmed by the details they have to deal with.

So as I was exploring some detail in one of my projects (currently: getting a once functioning renegade board to talk once again), I found this perfect opportunity to document my steps as I took a seemingly simple task (building a program from source), as there were a few hiccups along the way.

Thought process is important.

I did not write this code, yet that doesn't matter: I understood what the problems generally were, and was able to employ effective solutions, resulting in (this time) eventual success. What is important (as not everyone will necessarily have a need to build this particular tool, especially for my specific purposes) is that the underlying process is generally the same: see what is going on, and react intelligently to it.

Environment

It should be noted various environmental variables may well influence this process. Anything from system, OS, kernel version, version of build tools (compiler), and more.

Things will change (break) over time as well, so even these instructions may start to differ the more time has passed (and versions of things change).

For now (04/07/2018), this document was written using the following as my build environment:

  • system: ROC-RK3328-CC (renegade SBC, 4GB model) – ARM64
  • OS: Debian buster/Linux (my custom install)
  • kernel: currently using an appropriated 4.4.120 kernel from a ROCK64 build effort (4.4.120-rockchip-ayufan-209).
    • trivia: the ROCK64 is an SBC ALSO built using the RK3328 SoC. It has a few differences (doesn't use DDR4, has a physical power button, has some extra I/O pins). So: very similar, yet NOT EXACTLY THE SAME (they have different DTB files).
  • compiler: g++ (Debian 7.3.0-15) 7.3.0

It stands to reason that, any issues I have encountered, may NOT be encountered using the versions of these things RockChip uses in their development (likely points of contention: kernel and definitely compiler versions).

Just FYI.

Instructions

According to the build instructions (located in the Readme.txt file), to get everything working, we need to perform the following steps:

compile and install
1 install libusb and libudev
	sudo apt-get install libudev-dev libusb-1.0-0-dev dh-autoreconf
2 go into root of rkdeveloptool
3 autoreconf -i
4 ./configure
5 make

So let us proceed!

Install packages

From knowing we will be compiling stuff, to the specifics of what this particular project uses, we will need a functioning build environment, the means to clone the repo (git), and the particular libraries the project mentions by name:

Packages installed

  • build-essential
  • git
  • libudev-dev
  • libusb-1.0-0-dev
  • dh-autoreconf

Clone the repo

Should be straightforward, have URL will clone:

node00:/home/renegade# git clone https://github.com/rockchip-linux/rkdeveloptool.git
Cloning into 'rkdeveloptool'...
remote: Counting objects: 171, done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 171 (delta 0), reused 1 (delta 0), pack-reused 163
Receiving objects: 100% (171/171), 136.20 KiB | 2.43 MiB/s, done.
Resolving deltas: 100% (85/85), done.
node00:/home/renegade# 

autoconfigure

Next up is the autoreconf step:

node00:/home/renegade/rkdeveloptool# autoreconf -i
configure.ac:12: installing 'cfg/compile'
configure.ac:19: installing 'cfg/config.guess'
configure.ac:19: installing 'cfg/config.sub'
configure.ac:7: installing 'cfg/install-sh'
configure.ac:7: installing 'cfg/missing'
Makefile.am: installing 'cfg/depcomp'
node00:/home/renegade/rkdeveloptool# 

Configure

Then running the generated configure script:

node00:/home/renegade/rkdeveloptool# ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
...
checking build system type... aarch64-unknown-linux-gnu
checking host system type... aarch64-unknown-linux-gnu
checking for library containing iconv... none required
./configure: line 4478: syntax error near unexpected token `LIBUSB1,libusb-1.0'
./configure: line 4478: `PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)'
node00:/home/renegade/rkdeveloptool# 

libusb problems

Uh oh… something isn't happy with the current state of libusb. Is both the library and the headers installed? Let's check:

node00:/home/renegade/rkdeveloptool# dpkg -l | grep libusb
ii  libusb-1.0-0:arm64               2:1.0.21-2                     arm64        userspace USB programming library
ii  libusb-1.0-0-dev:arm64           2:1.0.21-2                     arm64        userspace USB programming library development files
node00:/home/renegade/rkdeveloptool# 

investigating line of reported error

That's an affirmative. And version 1.0 to boot… so, let's see what's happening on line 4478 in the configure script:

node00:/home/renegade/rkdeveloptool# cat -n configure | grep 4478
  4478	PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)
node00:/home/renegade/rkdeveloptool# 

looking for PKG_CHECK_MODULES

Okay, so this “PKG_CHECK_MODULES” macro/function is not finding a satisfactory presence of libusb-1.0. Time to see what it is testing for…

node00:/home/renegade/rkdeveloptool# cat -n configure | grep PKG_CHECK_MODULES
  4478	PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)
node00:/home/renegade/rkdeveloptool# 

Surrounding check

The only mention is the same as the problem experienced. So PKG_CHECK_MODULES is somewhere else. Let's broaden our view ever so slightly to the immediate surroundings:

node00:/home/renegade/rkdeveloptool# grep PKG_CHECK_MODULES *
Readme.txt:./configure: line 4269: `PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)'
grep: autom4te.cache: Is a directory
grep: cfg: Is a directory
configure:PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)
configure.ac:PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)
node00:/home/renegade/rkdeveloptool# 

Reading the Readme moar

Hey, you know what they say: when all else fails, read the documentation. Interesting that there is a specific mention of our line for the USB library in the Readme file, eh?

Let's see what it says:

    19	compile error help
    20	if you encounter the error like below:
    21	./configure: line 4269: syntax error near unexpected token `LIBUSB1,libusb-1.0'
    22	./configure: line 4269: `PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)'
    23
    24	You should install pkg-config libusb-1.0:
    25		sudo apt-get install pkg-config libusb-1.0

pkg-config to the rescue

Well, I KNOW I've got libusb-1.0 installed, but do I have pkg-config?

node00:/home/renegade/rkdeveloptool# dpkg -l | grep pkg-config
node00:/home/renegade/rkdeveloptool# 

Aha! Let's install that too:

node00:/home/renegade/rkdeveloptool# aptitude install pkg-config
The following NEW packages will be installed:
  pkg-config
0 packages upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 60.6 kB of archives. After unpacking 193 kB will be used.
Get: 1 http://deb.debian.org/debian buster/main arm64 pkg-config arm64 0.29-4+b1 [60.6 kB]
Fetched 60.6 kB in 0s (216 kB/s)
Selecting previously unselected package pkg-config.
(Reading database ... 26430 files and directories currently installed.)
Preparing to unpack .../pkg-config_0.29-4+b1_arm64.deb ...
Unpacking pkg-config (0.29-4+b1) ...
Setting up pkg-config (0.29-4+b1) ...
Processing triggers for man-db (2.8.3-1) ...

node00:/home/renegade/rkdeveloptool# 

Are we good now

And then continue with our configureing:

node00:/home/renegade/rkdeveloptool# ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
...
./configure: line 4478: syntax error near unexpected token `LIBUSB1,libusb-1.0'
./configure: line 4478: `PKG_CHECK_MODULES(LIBUSB1,libusb-1.0)'
node00:/home/renegade/rkdeveloptool# 

Clear out and start from scratch

Hmm…. maybe some sort of cached value? Let's just blow everything away and start from scratch (we're not that far into the process):

node00:/home/renegade/rkdeveloptool# cd ..
node00:/home/renegade# rm -rf rkdeveloptool/
node00:/home/renegade# git clone https://github.com/rockchip-linux/rkdeveloptool.git
Cloning into 'rkdeveloptool'...
remote: Counting objects: 171, done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 171 (delta 0), reused 1 (delta 0), pack-reused 163
Receiving objects: 100% (171/171), 136.20 KiB | 1.50 MiB/s, done.
Resolving deltas: 100% (85/85), done.
node00:/home/renegade# cd rkdeveloptool
node00:/home/renegade/rkdeveloptool# autoreconf -i
configure.ac:12: installing 'cfg/compile'
configure.ac:19: installing 'cfg/config.guess'
configure.ac:19: installing 'cfg/config.sub'
configure.ac:7: installing 'cfg/install-sh'
configure.ac:7: installing 'cfg/missing'
Makefile.am: installing 'cfg/depcomp'
node00:/home/renegade/rkdeveloptool# ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... no
...
checking pkg-config is at least version 0.9.0... yes
checking for LIBUSB1... yes
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating cfg/Makefile
config.status: creating cfg/config.h
config.status: executing depfiles commands
node00:/home/renegade/rkdeveloptool# 

And we have liftoff!

Building it all with make

The last step is simply to compile everything with 'make'. Make it so.

Running make

Onto the actual compilation process:

node00:/home/renegade/rkdeveloptool# make
make[1]: Entering directory '/home/renegade/rkdeveloptool'
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.cpp
main.cpp: In function ‘bool upgrade_loader(STRUCT_RKDEVICE_DESC&, char*)’:
main.cpp:1916:13: error: comparison is always false due to limited range of data type [-Werror=type-limits]
   if (index == -1) {
       ~~~~~~^~~~~
main.cpp:1940:13: error: comparison is always false due to limited range of data type [-Werror=type-limits]
   if (index == -1) {
       ~~~~~~^~~~~
cc1plus: all warnings being treated as errors
Makefile:462: recipe for target 'main.o' failed
make[1]: *** [main.o] Error 1
make[1]: Leaving directory '/home/renegade/rkdeveloptool'
Makefile:482: recipe for target 'all-recursive' failed
make: *** [all-recursive] Error 1
node00:/home/renegade/rkdeveloptool# 

Addressing build problems

Uh oh, moar problems.

First up, WHAT is the problem:

  • comparison is always false due to limited range of data type
  • syntax in question: if (index == -1)

From our C programming experiences, we are comparing a variable index against a numeric value of -1, and the compiler is telling us the comparison is always false based on the limited range of data type… well, let us go and see how index is being declared, shall we? (My suspicion: is it being declared as unsigned?)

node00:/home/renegade/rkdeveloptool# cat -n main.cpp | grep index
  1097		int i, index, pos;
  1115			if (fscanf(file, OPT_PATH "%d=%[^\r^\n]", &index, buf)
  1118			index--;
  1120			strcpy((char*)gOpts.code471Path[index], buf);
  1121			printf("path%i: %s\n", index, gOpts.code471Path[index]);
  1134		int i, index, pos;
  1152			if (fscanf(file, OPT_PATH "%d=%[^\r^\n]", &index, buf)
  1156			index--;
  1157			strcpy((char*)gOpts.code472Path[index], buf);
  1158			printf("path%i: %s\n", index, gOpts.code472Path[index]);
  1171		int i, j, index, pos;
  1195			if (fscanf(file, OPT_LOADER_NAME "%d=%s", &index, buf)
  1198			index--;
  1199			strcpy(gOpts.loader[index].name, buf);
  1200			printf("name%d: %s\n", index, gOpts.loader[index].name);
  1887		char index;
  1915			index = pBoot->GetIndexByName(ENTRYLOADER, loaderCodeName);
  1916			if (index == -1) {
  1922			bRet = pBoot->GetEntryProperty(ENTRYLOADER, index, dwLoaderSize, dwDelay);
  1932			if (!pBoot->GetEntryData(ENTRYLOADER, index, loaderCodeBuffer)) {
  1939			index = pBoot->GetIndexByName(ENTRYLOADER, loaderDataName);
  1940			if (index == -1) {
  1948			bRet = pBoot->GetEntryProperty(ENTRYLOADER, index, dwLoaderDataSize, dwDelay);
  1958			if (!pBoot->GetEntryData(ENTRYLOADER,index,loaderDataBuffer)) {

We see that index is being declared a number of times (likely a local variable across a number of functions). And knowing that, at least until the standards dictate otherwise, an int declaration implies signed, those should be good.

Yet there is one declaration of index that specifically isn't int, and that is on line 1887; our two problem lines (1916, 1940) follow that numerically:

  1887		char index;
  1916			if (index == -1) {
  1940			if (index == -1) {

When a char is not a type of int but something different

I've been seeing this behavior in recent years: not treating char types as the 1-byte numerical values they are (indeed, many modern compilers will yell and scream if your for() loop iterating variable is a char).

And it should be noted, these files are C++; I can definitely see C++ making some tighter distinction (through whatever divinations) that “chars are not numbers” in the same way that “ints are numbers”. Feh. Fine. Be all silly, C++ (again, from my more biased C point of view, where chars are just 1 byte of integer goodness). There may well be good reason, and I may not even disagree with it… but for now, this is the current reality: the compiler is unhappy about something that the authors were clearly expecting to work. They seem to think it is an okay use of char… and from my C background, I agree it seems a valid usage.

I bet our char declaration of index is the one impacting our two if() statement comparisons on lines 1916 and 1940:

  1878  bool upgrade_loader(STRUCT_RKDEVICE_DESC &dev, char *szLoader)
  1879  {
  1880          if (!check_device_type(dev, RKUSB_MASKROM))
  1881                  return false;
  1882          CRKImage *pImage = NULL;
  1883          CRKBoot *pBoot = NULL;
  1884          CRKComm *pComm = NULL;
  1885          bool bRet, bSuccess = false;
  1886          int iRet;
  1887          char index;
  ...
  1915                  index = pBoot->GetIndexByName(ENTRYLOADER, loaderCodeName);
  1916                  if (index == -1) {
  ...
  1939                  index = pBoot->GetIndexByName(ENTRYLOADER, loaderDataName);
  1940                  if (index == -1) {
  ...

Yep: this is all taking place within the upgrade_loader() function.

Fix: make index an int

So, a clear solution is to make this index ALSO an int, yet do we need to take into account any other issues?

For example, what is the GetIndexByName() function returning?

node00:/home/renegade/rkdeveloptool# grep -i getindexbyname *
RKBoot.cpp:char CRKBoot::GetIndexByName(ENUM_RKBOOTENTRY type,char *pName)
RKBoot.h:	char GetIndexByName(ENUM_RKBOOTENTRY type, char *pName);
grep: autom4te.cache: Is a directory
grep: cfg: Is a directory
main.cpp:		index = pBoot->GetIndexByName(ENTRYLOADER, loaderCodeName);
main.cpp:		index = pBoot->GetIndexByName(ENTRYLOADER, loaderDataName);
node00:/home/renegade/rkdeveloptool# 

There, in RKBoot.h, we see that GetIndexByName() returns a char. SO: to avoid generating other compiler messages, we need to properly handle this.

If we change index to an int, we should take care to treat it like a char when interacting with those other functions… so let us try some typecasting:

  • index declared as an int type (signed, by default).
  • cast return value of GetIndexByName() to an int
  • cast index, when passing into GetEntryData(), as a char

Changes, by impacted line number, look something like this:

node00:/home/renegade/rkdeveloptool# cat -n main.cpp | grep index
  ... ignoring everything before 1887, because they aren't in the function in question ...
  1887		int index;
  1915			index = (int) pBoot->GetIndexByName(ENTRYLOADER, loaderCodeName);
  1916			if (index == -1) {
  1922			bRet = pBoot->GetEntryProperty(ENTRYLOADER, (char) index, dwLoaderSize, dwDelay);
  1932			if (!pBoot->GetEntryData(ENTRYLOADER, (char) index, loaderCodeBuffer)) {
  1939			index = (int) pBoot->GetIndexByName(ENTRYLOADER, loaderDataName);
  1940			if (index == -1) {
  1948			bRet = pBoot->GetEntryProperty(ENTRYLOADER, (char) index, dwLoaderDataSize, dwDelay);
  1958			if (!pBoot->GetEntryData(ENTRYLOADER,(char) index,loaderDataBuffer)) {

Here is the diff

Using the diff(1) tool on my updated main.cpp, against a backup made before making changes (always a good idea), here is another representation of the alterations made (updates are before, original is after):

node00:/home/renegade/rkdeveloptool# diff main.cpp main.cpp.bak
1887c1887
< 	int index;
---
> 	char index;
1915c1915
< 		index = (int) pBoot->GetIndexByName(ENTRYLOADER, loaderCodeName);
---
> 		index = pBoot->GetIndexByName(ENTRYLOADER, loaderCodeName);
1922c1922
< 		bRet = pBoot->GetEntryProperty(ENTRYLOADER, (char) index, dwLoaderSize, dwDelay);
---
> 		bRet = pBoot->GetEntryProperty(ENTRYLOADER, index, dwLoaderSize, dwDelay);
1932c1932
< 		if (!pBoot->GetEntryData(ENTRYLOADER, (char) index, loaderCodeBuffer)) {
---
> 		if (!pBoot->GetEntryData(ENTRYLOADER, index, loaderCodeBuffer)) {
1939c1939
< 		index = (int) pBoot->GetIndexByName(ENTRYLOADER, loaderDataName);
---
> 		index = pBoot->GetIndexByName(ENTRYLOADER, loaderDataName);
1948c1948
< 		bRet = pBoot->GetEntryProperty(ENTRYLOADER, (char) index, dwLoaderDataSize, dwDelay);
---
> 		bRet = pBoot->GetEntryProperty(ENTRYLOADER, index, dwLoaderDataSize, dwDelay);
1958c1958
< 		if (!pBoot->GetEntryData(ENTRYLOADER,(char) index,loaderDataBuffer)) {
---
> 		if (!pBoot->GetEntryData(ENTRYLOADER,index,loaderDataBuffer)) {
node00:/home/renegade/rkdeveloptool# 

Another build attempt

Okay, now the pivotal question: does it build???

node00:/home/renegade/rkdeveloptool# make
make[1]: Entering directory '/home/renegade/rkdeveloptool'
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT main.o -MD -MP -MF .deps/main.Tpo -c -o main.o main.cpp
mv -f .deps/main.Tpo .deps/main.Po
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT crc.o -MD -MP -MF .deps/crc.Tpo -c -o crc.o crc.cpp
mv -f .deps/crc.Tpo .deps/crc.Po
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT RKBoot.o -MD -MP -MF .deps/RKBoot.Tpo -c -o RKBoot.o RKBoot.cpp
mv -f .deps/RKBoot.Tpo .deps/RKBoot.Po
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT RKComm.o -MD -MP -MF .deps/RKComm.Tpo -c -o RKComm.o RKComm.cpp
mv -f .deps/RKComm.Tpo .deps/RKComm.Po
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT RKDevice.o -MD -MP -MF .deps/RKDevice.Tpo -c -o RKDevice.o RKDevice.cpp
mv -f .deps/RKDevice.Tpo .deps/RKDevice.Po
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT RKImage.o -MD -MP -MF .deps/RKImage.Tpo -c -o RKImage.o RKImage.cpp
mv -f .deps/RKImage.Tpo .deps/RKImage.Po
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT RKLog.o -MD -MP -MF .deps/RKLog.Tpo -c -o RKLog.o RKLog.cpp
mv -f .deps/RKLog.Tpo .deps/RKLog.Po
g++ -DHAVE_CONFIG_H -I. -I./cfg  -Wall -Werror -Wextra -Wreturn-type -fno-strict-aliasing -D_FILE_OFFSET_BITS=64 -D_LARGE_FILE -I/usr/include/libusb-1.0   -g -O2 -MT RKScan.o -MD -MP -MF .deps/RKScan.Tpo -c -o RKScan.o RKScan.cpp
mv -f .deps/RKScan.Tpo .deps/RKScan.Po
g++  -g -O2   -o rkdeveloptool main.o crc.o RKBoot.o RKComm.o RKDevice.o RKImage.o RKLog.o RKScan.o -lusb-1.0
make[1]: Leaving directory '/home/renegade/rkdeveloptool'
node00:/home/renegade/rkdeveloptool# 

YES. We have the tool.

Important takeaways

As you approach building software, especially that developed by others, there are some common strategies I find I use over and over again:

  • never expect things to build cleanly out of the gate (sure, sometimes they will, but let that be a bonus rather than the expectation)
  • versions matter (especially if you are not caring about what they used going on; or are intentionally using likely newer things)
  • the documentation is there for a reason. We should always know where it is, even if we do not start off by reading it (or reading it from start to finish)
  • error messages are very important and informative: READ AND COMPREHEND them.
  • know your APIs: be aware return types and parameter types
    • moreso: know how to locate function declarations (usually in header files)
  • make connections: don't just react to one problem- view it in the scope of the environment it exists.
  • have adequate information processing bandwidth (likely a challenge for many): there is a lot of information here. I had to sift through a great deal. There is no “too long, didn't read” aspect to this. If too much information stops you dead in your tracks, you're not completing this.
  • have adequate information filtering: yes, there was a lot of information, but through using tools (like grep), I really only had to look at a small fraction of the available information. And note that I still took care to view the immediate surroundings, and NOT just the lines in question (line 1916 had a distinct problem, yet 1915 was also involved in my solution).
haxx/projects/sbc/rkdeveloptool.txt · Last modified: 2018/04/07 09:55 by wedge