Clicca qui per iscriverti.
Over time, I find myself more and more interested in CMake. That’s because I write many little programs, and those that use makefiles routinely break as I switch OS.
The last one was a program that depended on libpng. When I tried to compile it on Mac OS X, it failed to find the library. That’s because on Linux libpng.so is simply in /usr/lib and png.h is in /usr/include, both of which are in the compiler’s search path, so that all you need to do is add “-lpng” when linking.
But on a Mac libpng.dylib (yes, shared libraries on a Mac have .dylib extension) is in /usr/X11/lib and png.h is in /usr/X11/include that are not in the compiler’s search path, so that when compiling you need to add “-I/usr/X11/include” and when linking “-L/usr/X11/lib -lpng”.
The solution was not to keep two separate makefiles, but rather to throw away the makefile and replace it with a CMakeLists.txt
Which brings us to the question: how to link with libraries using CMake?
There are two ways, the first is the find_package command, the other is the find_library command.
Let’s start with find_package. CMake “knows” about many widely used libraries. For them, there is a script to find them in all supported platforms. So, to use a library all you need to do is find it with the find_package command. Here is a simple example of a program that uses threads and so depends on “-lpthread”, main.cpp:
#include <iostream> using namespace std; void *thread(void *argv) { cout<<"Into a spawned thread"<<endl; } int main() { pthread_t t; pthread_create(&t,NULL,thread,NULL); pthread_join(t,NULL); cout<<"Back in main thread"<<endl; }and here is the CMakeLists.txt file:
cmake_minimum_required(VERSION 2.6) project(TEST) ## Target set(TEST_SRCS main.cpp) add_executable(test ${TEST_SRCS}) ## Link libraries find_package(Threads REQUIRED) target_link_libraries(test ${CMAKE_THREAD_LIBS_INIT})As can be seen, the first parameter passed to find_package is the name of the package, the second is “REQUIRED” and means that if the library could not be found, CMake should stop and print an error message.
Once the library is found, you have to say which executable needs it (because a single CMakeLists.txt can be used to produce many executable by just using more add_executable commands). This is achieved with the target_link_libraries command that appends a library to the list of libraries an executable needs. The first parameter is the executable name, the second is the library. Note that find_library generates a variable that contains the name of the library, in this case the name is CMAKE_THREAD_LIBS_INIT. This strange name is an exception, usually all find_package scripts create a variable with the name <libraryname>_LIBRARY.
Now a more complex example: the libpng issue I talked about earlier. It is more complex because you don’t just need to add a library when linking, you also need to tell the compiler where is the png.h file when compiling. Luckily, CMake has a package for libpng that does all that, and here is the CMakeLists.txt example:
cmake_minimum_required(VERSION 2.6) project(TEST) ## Targets set(TEST_SRCS test.cpp) add_executable(test ${TEST_SRCS}) ## Link libraries find_package(PNG REQUIRED) include_directories(${PNG_INCLUDE_DIR}) target_link_libraries(test ${PNG_LIBRARY})The find_package command finds the PNG library, target_link_libraries adds the library to the list of libraries the executable needs, and include_directories adds the directory where the .h file is when compiling.
But this isn’t the end. Other than libraries there are collections of libraries. And CMake supports them too. One example are the boost libraries. There is no single libboost.so to link to; instead every sub-library has its .so file. So there should be a way to link only with the desired sub-libraries. This is an example CMakeLists.txt that does that:
cmake_minimum_required(VERSION 2.6) project(TEST) ## Target set(TEST_SRCS main.cpp) add_executable(test ${TEST_SRCS}) ## Link libraries set(BOOST_LIBS thread date_time system) find_package(Boost COMPONENTS ${BOOST_LIBS} REQUIRED) target_link_libraries(test ${Boost_LIBRARIES}) find_package(Threads REQUIRED) target_link_libraries(test ${CMAKE_THREAD_LIBS_INIT})In this case we initialize a variable with the sub-libraries we want (boost.thread, boost.date_time and boost.system). Then we call find_package with the library name (Boost), the word COMPONENTS followed by the list of sub-libraries and as usual the REQUIRED word. Since boost.thread depends on the system’s thread library, we also use another find_package command to link with threads.
This ends the examples of find_package, but there is one last issue: what if we need a library for which there isn’t a package script? The solution is to use the find_library command. It will search in the system paths for the needed library. Here is an example that uses the command to find the Poco libraries:
cmake_minimum_required(VERSION 2.6) project(TEST) ## Target set(TEST_SRCS main.cpp) add_executable(test ${TEST_SRCS}) ## Link libraries find_library(POCO_FOUNDATION PocoFoundation) find_library(POCO_NET PocoNet) target_link_libraries(test ${POCO_FOUNDATION} ${POCO_NET}) find_package(Threads REQUIRED) target_link_libraries(test ${CMAKE_THREAD_LIBS_INIT})The find_library command takes two parameters, the first is the variable where the found library will be stored, and the second is the library name (the name is camelcase in this example because Poco libraries are camelcase, the library name is really libPocoFoundation.so).
References: CMake wiki
It took some time but finally someone put on the web images of what’s inside the Fonera 2.0N.
Here are the links:
Printed circuit top
Printed circuit bottom
Now some details:
In the top picture you can clearly see the new Ralink chip and the FLASH memory, which has a parallel interface (the previous Foneras had a serial FLASH) so we can expect faster boot times and better read/write performance.
The bottom picture only shows the two RAM chips.
There seems to be a JTAG port (the 12 pads at the left of the top image). Even the other Foneras had such connector, but was never used since redboot and the serial port provided an easier way to reflash it.
And yes, there is also a serial port! The connector name is JP2, the 4 pin connector at the bottom left in the top pcb image. It is not populated by default, but it is always possible to solder wires directly to the pad on the pcb.
Quoting from
http://fonosfera.org/pipermail/development/2010-January/001309.html
Serial Port is JP2.
Pins are 3,3V – RXD – GND – TXD
Configuration is: 57600 8 N 1
As I don’t have a Fonera 2.0N, I can’t try to see if it really works, buth the pinout does make sense since it says that GND is pin 3, and the bottom pcb image shows that the third pin is connected to the ground plane.
I posted a new article on my website.
It talks about a performance optimized and high level way of handling STM32 GPIOs using Template Metaprogramming.
It’s not the first time I talk about CMake in this blog, for the introduction read here. Now it’s time to explore the CMake syntax further. The first CMakeLists.txt looked like this:
cmake_minimum_required(VERSION 2.6) project(HELLO) set(HELLO_SRCS main.cpp foo.cpp) add_executable(hello ${HELLO_SRCS})As already explained, it successfully creates an executable called “hello” using the main.cpp and foo.cpp source files. But let’s see exactly what CMake does to compile these files. To do so, it is possible to use the commands:
mkdir build && cd build cmake ../ make VERBOSE=1The interesting thing here is the VERBOSE=1 option. By default CMake hides the options passed to the compiler, and displays a higher level status indicator with the build completion percentage together with the name of the file currently being built (a much more elegant solution than autoconf). But if the goal is to see the compiler flags used, it is always possible to override this behaviour with the VERBOSE=1 option.
Here is the relevant part of the printout:
[...] [ 50%] Building CXX object CMakeFiles/hello.dir/main.cpp.o /usr/bin/c++ -o CMakeFiles/hello.dir/main.cpp.o -c /tmp/cmaketest/main.cpp [100%] Building CXX object CMakeFiles/hello.dir/foo.cpp.o /usr/bin/c++ -o CMakeFiles/hello.dir/foo.cpp.o -c /tmp/cmaketest/foo.cpp Linking CXX executable hello /usr/bin/c++ CMakeFiles/hello.dir/main.cpp.o CMakeFiles/hello.dir/foo.cpp.o -o hello -rdynamic [...]Now, if you’ve read my previous blog post on GCC’s compiler flags, you might probably not like what you see, since no optimization flag has been passed to GCC and as a result, your program won’t run as fast as it should.
That’s because no build type has been specified to CMake. The build type is a feature most IDE have, it allows you to compile your program in “debug” mode, for easily single-stepping through it with a debugger, or in “release” mode, with speed optimization enabled.
To fix this you simply need to specify a build type in the CMakeLists.txt file, in this way:
set(CMAKE_BUILD_TYPE Release)at the end of your CMakeLists.txt file. Of course, change “Release” with “Debug” for debug builds.
With the Release build type, the options passed to the compiler are these:
[...] [ 50%] Building CXX object CMakeFiles/hello.dir/main.cpp.o /usr/bin/c++ -O3 -DNDEBUG -o CMakeFiles/hello.dir/main.cpp.o -c /tmp/cmaketest/main.cpp [100%] Building CXX object CMakeFiles/hello.dir/foo.cpp.o /usr/bin/c++ -O3 -DNDEBUG -o CMakeFiles/hello.dir/foo.cpp.o -c /tmp/cmaketest/foo.cpp Linking CXX executable hello /usr/bin/c++ -O3 -DNDEBUG CMakeFiles/hello.dir/main.cpp.o CMakeFiles/hello.dir/foo.cpp.o -o hello -rdynamic [...]Much better than before. And if you find uncomfortable to have to edit the CMakeLists.txt file to switch between Release and Debug mode, you can also specify the option in the CMake command line, like this:
mkdir build && cd build cmake -D CMAKE_BUILD_TYPE=Debug ../ makeLast, if you are a GCC wizard and you want full control of the options passed to the compiler, you can also set them manually. However, keep in mind that a CMakeLists.txt file should ideally work with many compilers. So before forcing compiler options, you need to check that the compiler is really GCC. This is an example that shows how to do it:
cmake_minimum_required(VERSION 2.6) project(HELLO) ## Target set(HELLO_SRCS main.cpp foo.cpp) add_executable(hello ${HELLO_SRCS}) ## Compiler flags if(CMAKE_COMPILER_IS_GNUCXX) list(APPEND CMAKE_CXX_FLAGS -O2) ## Optimize list(APPEND CMAKE_EXE_LINKER_FLAGS -s) ## Strip binary endif()Note that you must not specify a build type (Debug or Release) since it apparently conflicts with the manually set compiler flags.
Last note, not necessarily CMake related, if you happen to have a multicore CPU and want to speed up builds, you can use the -jX option of make, where X is the number of cores in your CPU. It tells make to compile X files (lol ) at the same time. So for a dual core, use:
mkdir build && cd build cmake ../ make -j2The most common use of a Fonera is as wireless access point, but it’s not the only possible use. Another possibility is to use the Fonera to connect to an existing wireless network. This can be useful to connect a computer without a wireless card, to set up a “repeater” to extend the range of a wireless network, or to run some application that needs internet connectivity on the Fonera itself (after all, it’s just a Linux based device).
However, when the network you need to connect to requires a WPA enterprise certificate authentication, things can get messy.
This post is just a log of all I had to do to connect my Fonera to one such network, and I think it can be useful to other who have the same need.
First, I’ll describe how to connect to that network using a computer running Linux, using the shell only. It is also possible to use some GUI utility, such as network manager but the shell way is what (in theory) can be usd on the Fonera too, since it has no GUI.
The procedure starts by creating a wpa_supplicant.conf file, with the following content:
And by filling in the required data where there are angle brackets. It is also a good idea to write all the commands to connect in an .sh script file, just like this:
sudo /etc/init.d/network-manager stop sudo killall wpa_supplicant sudo ifconfig eth1 down sudo ifconfig eth1 up sudo iwconfig eth1 essid <your network ssid> sudo wpa_supplicant -i eth1 -D wext -c <path to wpa_supplicant.conf>/wpa_supplicant.conf -d & #wait for connection sleep 20 sudo dhclient eth1The script uses a couple of tricks: first it stops network-manager, since it interferes with the manual connection (don’t worry, it will be started again next time you reboot your computer), then the sleep 20 at the end is there to give time to wpa_supplicant to connect to the network before dhclient starts. Of course if you use this you need to replace eth1 with the device name of your wireless card.
This works flawlessly on a Linux computer, but when I tried to connnnect in this way on a Fonera running OpenWrt, it failed. First the Fonera does not have network-manager, so the first line needs to be removed. Then there is no dhclient, but an equivalent program named dhcpcd. But the real problem is wpa_supplicant. When I started it, it failed with the following error:
RSA: Expected zero INTEGER in the beginning of private key; not found TLSv1: Failed to parse private key TLS: Failed to load private key TLS: Failed to set TLS connection parameters EAP-TLS: Failed to initialize SSL. TLSv1: Selected cipher suite: 0x0000 TLSv1: Record Layer - New write cipher suite 0x0000 TLSv1: Record Layer - New read cipher suite 0x0000 EAP: Failed to initialize EAP method: vendor 0 method 13 (TLS)After searching and posting on the OpenWrt forum, the problem was found: to minimize the size of the OpenWrt firmware (some routers only have 2..4MB of FLASH memory…) wpa_supplicant is compiled with an internal (and incomplete) implementation of TLS, which may fail with some certificate types. The solution is to compile a custom wpa_supplicant configured to use OpenSSL as TLS provider.
So I downloaded on my computer running Kubuntu Linux the OpenWrt buildroot with
svn co svn://svn.openwrt.org/openwrt/branches/8.09configured it with
make meunconfigin the following way: Network > wpa_supplicant > TLS provider > OpenSSL and disabled timestamp check (since the Fonera does not have a permanent clock and the time is set to 1/1/1970 every time it reboots)
Then I typed “make” and after ~2GB of source files downloaded form the Internet and 1.5 hours of compile time the buildroot compiled a custom firmware with the required packages.
However when I installed the wpa-supplicant_0.6.3-1.1_mips.ipk file together with the required dependencies libopenssl_0.9.8i-3.2_mips.ipk and zlib_1.2.3-5_mips.ipk on my OpenWrt it still failed to connect. The problem this time was a lot of ioctl() errors. It looks like the wpa_supplicant package is heavily dependent on the kernel version, so it didn’t work.
The solution was to reflash the Fonera with the firmware that the buildroot compiled together with the wpa_supplicant package. At this point another problem occurred: in the custom firmware the ath0 device, which is the wireless device was not present! At the beginning the problem looked like the lack of the kmod_madwifi package, but the package was present. The solution was to create the device at every reboot with
wlanconfig ath0 create wlandev wifi0Now that the device was again available, a new problem occurred: wpa_supplicant successfully parsed the certificates, but failed agian to connect, with the following error:
TLS: Certificate verification failed, error 9 (certificate is not yet valid)The problem was that while the timestamp check was disabled, the date still needed to be within the certificate’s validity range. A quick “date -s” command soved this and finally wpa_supplicant connected to the wireless network.
However, it failed getting an IP address. This because in my custom firmware I forgot to add dhcpcd. Building the dhcpcd package with the buildroot and installing the .ipk package solved this last problem.
In the end the scripts used to connect successfully are these:
wpa_supplicant.conf
ctrl_interface=/var/run/wpa_supplicant ctrl_interface_group=0 ap_scan=1 network={ ssid="<your network ssid>" proto=WPA key_mgmt=WPA-EAP auth_alg=OPEN pairwise=TKIP eap=TLS anonymous_identity="<your username>" ca_cert="<your path to the .cer certificate file>" private_key="your path to the .p12 certificate file>" private_key_passwd="<your password>" phase2="auth=MSCHAPV2" }connect.sh
## For Fonera + specially compiled OpenWrt ## Developed by TFT ## any date in the certificate validity range is acceptable date -s 2009.11.20-10:00 killall wpa_supplicant dhcpcd wlanconfig ath0 destroy wlanconfig ath0 create wlandev wifi0 ifconfig ath0 down ifconfig ath0 up iwconfig ath0 essid <your network ssid> wpa_supplicant -i ath0 -D madwifi -c /etc/wpa_supplicant.conf -d & ## wait for connection sleep 20 dhcpcd ath0