Part -1 of this series got us started on accessing the Linksys E1200 router's circuit board and exploring the operating system. Part-2 then went on to making software changes and replacing the router's factory firmware with a custom one. This project will get you started on the basics of accessing GPIOs on Linux and how to control the router's onboard LED. So, Let's get started!
The Linksys E1200 has a total of five LEDs on its circuit board. One LED (located under the reset button) shows the power status and the remaining four (located under the LAN ports) show the data transmission status.
The LED under the rest button is the only one among the five to be connected to the processor's GPIO pins. The remaining ones are connected to the Ethernet MAC. That, unfortunately for us, means that we can only control one LED. But that's okay. We can do a lot with just that one LED.
We will be using the sysfs pseudo filesystem provided by Linux to control the GPIO pins. Assuming you have access to the router's terminal (either through SSH or UART as shown in Part-1 and Part-2), let's start by exploring the GPIO system under sysfs folder on the filesystem.
root@OpenWrt:# cd /sys/class/gpio
export gpiochip0 unexport
As you can see, this folder has three items. Each processor has a certain number of GPIO controllers and they are listed in this folder as gpiochipN. N indicates the chip number. In our case, we just have one GPIO chip controlling all the pins. Hence, the name gpiochip0. The BCM5357 processor on this router has a certain number of GPIO pins. We will see how to find the exact number of pins available later on in this series. We would need to know the pin number associated with the LED in order to control it. Now, how exactly do you find that pin number? Trial and Error!
In order to control the GPIO pin, we need to execute a few commands. The following line creates a file called gpio1 as shown below which controls pin number 1.
root@OpenWrt:# echo 1 >/sys/class/gpio/export
root@OpenWrt:# ls
export gpio1 gpiochip0 unexport
Once we create the file for the pin number, we change the direction. We can choose to make it output or input pin by writing out and in respectively to the direction file. Here, we make it an output pin.
root@OpenWrt:# echo out > /sys/class/gpio/gpio1/direction
We are now ready to turn on the pin by writing the value "1" to the value file as follows.
root@OpenWrt:# echo 1 > /sys/clas/gpio/gpio1/value
Now comes the trial and error part. We keep trying the same set of commands each time increasing the pin number until we see the LED turn on or off. Luckily, for this router, I was able to hit the LED pin number quickly as it was pin number 6. But there was something weird happening. When we write the value "1", the LED turns off and vice versa. This is because the GPIO pin is in an active low state. Great! We found our LED pin number. For an in-detail tutorial on how to use the sysfs filesystem refer to this tutorial.
Typing this set of commands each time you want to control the LED is time taking and non-productive. We, therefore, automate this by writing a C program. We will not be able to write and compile code directly on the router as this is a memory-constrained system (remember we only have 8MB of flash memory out of which 5Mb is taken up by the OS). Hence, we will write and build our code on a PC and then transfer the compiled binary file to the router.
Firstly, we need to check which C library is currently being used by all programs on the router. GCC is usually used on most desktop computers. Smaller systems like this router usually use a lighter version. We check the library used by seeing the out of the Dynamic loader as follows.
root@OpenWrt:# ldd
musl libc (mipsel-sf)
Version 1.1.24
Dynamic Program Loader
Usage: ldd [options] [--] pathname
As you can see, the router is using the MUSL library indicated by the first line in the output. Now, we need a cross compiler that can run on our laptop (x86 based) to generate binaries for the router which is MIPS based. Let's download that now. We visit this link which has all the toolchains based on MUSL. We then need to search for the one we need by matching the output of the ldd command we executed before and the list of toolchains available here. Our ldd output shows mipsel-sf. So we download the closest one available shown below.
Once the download is done, we extract the compressed folder. The following command extracts the file into the same downloads folder where we downloaded the zip file.
root@OpenWrt:# tar -zxvf ~/Downloads/mipsel-linux-muslsf-cross.tgz
We now add the path of the toolchain to the system path as follows. This is done so that the compiler is available system-wide.
root@OpenWrt:# export PATH="~/Downloads/mipsel-linux-muslsf-cross/bin:$PATH"
Once that is done we can verify by typing in mips on the terminal and pressing the TAB key. As you can see below, all the available tools show up here. I have a few other toolchains installed, so they are listed here too.
What we require here is the mips-linux-muslsf-gcc which is our compiler that will be used to generate binaries for our router. The code for this example is in the repository listed in the attachments section. We now open an editor and type in or LED blinky code (led_blinky.c). We then compile our C program as follows.
user@Laptop:$ mipsel-linux-muslsf-gcc -o led_blinky led_blinky.c
user@Laptop: ls
led_blinky led_blinky.c
Once the compilation is done, we can see the executable named led_blinky. We have dynamically compiled the executable here as we will be depending upon the musl C library on the router. This is done so that the size of the executable is kept as small as possible. As you can see from the output below, the executable is just 7kb.
user@Laptop:$ ls -lh
total 12K
-rwxrwxr-x 1 linuxdev linuxdev 7.0K Nov 6 17:42 led_blinky
-rw-rw-r-- 1 linuxdev linuxdev 1.8K Nov 6 17:30 led_blinky.c
As a side note, what if we compile the executable to be statically linked? The "-static" flag is passed to the compiler this time and you can see from the output below that if we do that, the size (25Kb) is significantly larger.
user@Laptop:$ mipsel-linux-muslsf-gcc -static -o led_blinky led_blinky.c
user@Laptop:$ ls -lh
total 40K
-rwxrwxr-x 1 linuxdev linuxdev 7.0K Nov 6 18:16 led_blinky
-rw-rw-r-- 1 linuxdev linuxdev 1.8K Nov 6 17:30 led_blinky.c
-rwxrwxr-x 1 linuxdev linuxdev 25K Nov 6 18:16 led_blinky_static
Okay, back to the LED part. Now that we have the compiled executable, we need to transfer it to the router. We do this using the scp tool provided by openssh. We have installed openssh on the router in part -2 specifically for this purpose. We transfer the file as follows.
user@Laptop:$ scp led_blinky root@192.168.1.1:/home/
root@192.168.1.1's password:
led_blinky 100% 7164 88.5KB/s 00:00
Note that I have created a home folder on the router to store all our projects. The output indicates that our project was successfully transferred. The next step is to execute our program.
root@OpenWrt:# ./led_blinky
Turning LED ON
Turning LED OFF
Turning LED ON
Turning LED OFF
Turning LED ON
As you can see, our program successfully runs and prints out the LED status. The LED also starts blinking.
We type in the key combination "Ctrl + c" to stop the program. Now, what if we try to run the program again? The program fails and shows an error.
root@OpenWrt:# ./led_blinky
Error writing to /sys/class/gpio/export: Resource busy
This is because our code tries to export the GPIO pin 6 again when we execute the program. The file descriptor for this file is still in use as we have not terminated it properly. We will fix this in two ways - First, by unexporting the GPIO file every time we run the code as shown below.
root@OpenWrt:# cd /sys/class/gpio
root@OpenWrt:/sys/class/gpio# ls
export gpio6 gpiochip0 unexport
root@OpenWrt:/sys/class/gpio# cat 6 > unexport
root@OpenWrt:/sys/class/gpio# ls
export gpiochip0 unexport
This is not really efficient and we'll go with solving this problem in the code itself by adding a bit of signaling to our code so that we terminate properly. This way, every time we hot "Ctrl + c", the code gracefully exits after unexporting the GPIO file and we can go about executing our program again and again. All the code files can be cloned via the GitHub repository linked in the attachments section.
static void Cleanup(void){
printf("\nSIGINT Trapped! Cleaning up and exiting...\n");
/* Open the gpio unexport file*/
u_int32_t fd = open("/sys/class/gpio/unexport", O_WRONLY);
/* Error check*/
if (fd < 0){
perror("Unable to open /sys/class/gpio/unexport");
exit(EXIT_FAILURE);
}
if (write(fd, LED_PIN, 2*sizeof(char)) != 2) {
perror("Error writing to /sys/class/gpio/unexport");
exit(EXIT_FAILURE);
}
close(fd);
exit(EXIT_SUCCESS);
}
As you can see in the above snippet, we do the unexporting part in the signal handler. Whenever we press the "Ctrl + c" key combination, this handler gets called and the pin is unexported. We then exit the program.
That would be all for this project. In the next project, we will build upon the learnings here and will be using the HTTP server on the router and creating our own webpage. The webpage will then be used to toggle the LED. Stay tuned!
Comments
Please log in or sign up to comment.