ESP8266: How to register partitions

Starting with v3.0, the ESP8266 NONOS SDK requires that partitions be registered in function void user_pre_init(void), like this:

#define SPI_FLASH_SIZE_MAP 2
void ICACHE_FLASH_ATTR user_pre_init(void)
{
        bool rc;
        static const partition_item_t part_table[] =
        {
                {SYSTEM_PARTITION_RF_CAL, 0xfb000, 0x1000},
                {SYSTEM_PARTITION_PHY_DATA, 0xfc000, 0x1000},
                {SYSTEM_PARTITION_SYSTEM_PARAMETER, 0xfd000, 0x3000},
        };
        rc = system_partition_table_regist(part_table,
                        sizeof(part_table)/sizeof(part_table[0]),
                        SPI_FLASH_SIZE_MAP);
        if (rc)
                goto done;
        os_printf("Error while registering partitions\r\n");
        while (1) ;
done:
}

The code above initialises and registers a table of partitions, each having a type, a start address, and a size. Despite the documentation in ESP8266 Non-OS SDK API Reference and README, partitions can still be difficult to understand. This post aims to provide some clarity.

As an aside—

Some images are mandatory for ESP8266 to work, as
described in ESP8266 SDK Getting Started Guide (see screenshot below).

They are obtained in bin of the SDK code repository and can be written to the flash as per the instructions. The addresses here are for the ESP-01 but can be found in the documentation for other models.

  • blank.bin at address 0xFB000
  • esp_init_data_default.bin at 0xFC000
  • blank.bin at 0xFD000

The bootloader and user program are written normally at 0x0000 and 0x1000 respectively. It is not necessary to rewrite the mandatory images every time, as long as they have not been overwritten. If in doubt, erase all the flash and write them again.

Back to the partitions—

The choice of partitions’ addresses is driven by the flash size on the target ESP8266 controller. Referring to the flash map given below and the size of each partition, it is easy to calculate the start addresses by starting with the last partition and working backwards.

Consider the 1 MiB flash of the ESP-01, with a size of 0x100000 bytes (1024 KiB).

The SYSTEM_PARTITION_SYSTEM_PARAMETER partition at the last position must be 12 KiB large. Therefore, its start address must be at 0x100000 – 0x3000 (0x3000 being hex for 12288), giving a value of 0xFD000.

The second to last partition, SYSTEM_PARTITION_PHY_DATA, must be 4 KiB large. Using the same math, 0xFD000 – 0x1000 (0x1000 being hex for 4096) the start address is determined to be 0xFC000

The first partition, SYSTEM_PARTITION_RF_CAL, with a size of 4 KiB, is then found to start at 0xFC000 – 0x1000, which is 0xFB000.

These calculated start addresses correspond to the values in the code above—and the addresses where the mandatory images are written.

The last argument passed to system_partition_table_regist is the “map”. In the example above, the macro SPI_FLASH_SIZE_MAP is defined with a value of 2. Where does it come from?

The enum flash_size_map in include/user_interface.h lists all possible values; in this case, the one corresponding to the 1 MiB flash of an ESP-01 (or 8M bits map) is 2.

Fixing low sound volume in Linux

If your audio output suddenly sounds much lower than usual, check the volume settings of Advanced Linux Sound Architecture (ALSA). Run the command alsamixer on the console and verify that the volume is as expected.

If you have more than one audio device on your computer (e.g., HDMI audio, built-in audio, USB audio, etc.) cycle through the devices with F6 and check that the volumes are set correctly.

As I understand it, ALSA is the kernel component of the audio stack in Linux, providing the API used by audio applications. Even if volume control applications (such as Pulse Audio Volume Control) show the volume at 100%, ALSA might be outputting sound at a lower level.

Batch script to open a file in a running instance of gVim

To open a file in a gVim (graphical Vim) instance that is already running, the following command is used.

C:\> gvim --remote-silent <filename>

To reduce the amount of typing needed, we can put the same command in a batch script file. We can also add a condition that starts a new instance of gVim or reuses an existing instance.

REM gvimr.bat
@echo off
tasklist /fi "imagename eq gvim.exe" 2>NUL | findstr /i gvim\.exe 2>NUL
if "%ERRORLEVEL%" == "1" (
    call gvim.bat --servername GVIMR %*
) else (
    if NOT "%~1" == "" (call gvim.bat --servername GVIMR --remote-silent %*)
)

Placement of -l options matters in GCC

I ran into an interesting puzzle with GCC this afternoon when trying to compile the code below.

#include <math.h>
#include <stdio.h>

int main(int argc, char **argv)
{
    long double x = 2;
    long double y = 1024;

    long double result = powl(x, y);
    printf("%LF ^ %LF : %LF\n", x, y, result);
    return 0;
}

The first command that I used to compile the code failed:

jeyoung@LENNY:~/Temp$ cc -lm -o powltest powltest.c
/usr/bin/ld: /tmp/ccfqvvbz.o: in function `main':
powltest.c:(.text+0x2e): undefined reference to `powl'
collect2: error: ld returned 1 exit status
jeyoung@LENNY:~/Temp$

After about one hour of trying to resolve this error, I was resigned to reading the documentation. This is what I found from the section Options for Linking.

-l library

Search the library named library when linking. (The second alternative with the library as a separate argument is only for POSIX compliance and is not recommended.)

The -l option is passed directly to the linker by GCC. Refer to your linker documentation for exact details. The general description below applies to the GNU linker.

The linker searches a standard list of directories for the library. The directories searched include several standard system directories plus any that you specify with -L.

Static libraries are archives of object files, and have file names like liblibrary.a. Some targets also support shared libraries, which typically have names like liblibrary.so. If both static and shared libraries are found, the linker gives preference to linking with the shared library unless the -static option is used.

It makes a difference where in the command you write this option; the linker searches and processes libraries and object files in the order they are specified. Thus, ‘foo.o -lz bar.o’ searches library ‘z’ after file foo.o but before bar.o. If bar.o refers to functions in ‘z’, those functions may not be loaded.

After changing the position of -lm, the code was compiled successfully.

jeyoung@LENNY:~/Temp$ cc -o powltest powltest.c -lm
jeyoung@LENNY:~/Temp$ ./powltest
2.000000 ^ 1024.000000 : 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216.000000
jeyoung@LENNY:~/Temp$

Debian Linux, kernel 5.19, and crypttab

A Debian Linux system, running kernel 5.19 with Linux Unified Key Setup (LUKS) encryption, sometimes fails to boot from the suspend state, with the error message: Gave up waiting for suspend/resume device.

This failure does not happen on kernel 5.18 and seems related to how the swap partition is set up in LVM volumes. For example, it occurs on my laptop with the swap partition in a volume group spanning two encrypted devices, but not on my desktop with the same partition in a volume group on a single encrypted device.

To resolve this error, identify the device that cannot be mounted at boot and add option initramfs to its corresponding entry in /etc/crypttab. This change forces the device to be opened during the initramfs stage at boot. (Typically, the device should automatically be identified and set up during this stage, as it was in kernel 5.18.)

Note that option initramfs is supported in only the Debian version of /etc/crypttab.

Two-finger scrolling in GNOME

For two-finger scrolling to work consistently in GNOME Wayland, I have to place one finger on the touchpad before the other. In addition, the point of contact of the second finger must be higher than the first’s.

I observed this behaviour with a Lenovo ThinkPad. I cannot say whether it is unique to this laptop or it is by design in GNOME Wayland.

UPDATE 29/06/2022: Two-finger scrolling appears to be improved on latest testing release. I no longer have to be so precise when touching the pad with my fingers.

Solving error 1962 with Debian and UEFI on IdeaCentre K430

When setting up Debian Linux (Bullseye) on my Lenovo IdeaCentre K430 computer, I encountered problems related to UEFI. After the installation, the system did not boot and displayed the message Error 1962: No operating system found. It took three attempts before I found the solution, which I describe here.

First, ensure that UEFI is enabled in the BIOS before starting the installation. The Debian installer shows whether the system is booted with UEFI—look out for the message on the Welcome screen.

Second, create an EFI System Partition (ESP) when configuring the disk. This will contain the EFI firmware that allows Debian to boot in UEFI mode. If UEFI is unavailable, you will not see this option.

Third, proceed with installation until it completes. Reboot when prompted but expect to see error 1962. You need a few more steps to fix it.

Fourth, boot into the Debian installer again and choose Rescue mode. Mount the /root, /boot, and /boot/efi partitions when asked. Then, open a console into /root from the Debian installer menu.

Fifth, copy /boot/efi/EFI/debian/grubx64.efi to /boot/efi/EFI/boot/bootx64.efi. The reason for doing this is documented. This is a crucial step.

Lastly, go into the BIOS setup, enable UEFI and disable CSM to force UEFI boot mode only. Also disable OS optimised settings—those are targeted at Windows 8 operating system.

Now, when you restart your computer, you should not get error 1962 and should boot directly into Debian.

How to preserve custom /etc/resolv.conf entries with DHCP in Debian Linux

In Debian Linux, when a network interface is configured to obtain an IP address automatically, the DHCP client utility writes the values received from the server over the content of file /etc/resolv.conf. Thus, custom entries that you had saved in this file, typically domain suffixes and domain name servers, are lost. If you need DHCP but also want to keep your configuration, do this:

  1. Edit file /etc/dhcp/dhclient.conf.
  2. Add the following lines, adjusting the values to match your needs.
supersede domain-name "my-domain.com";
supersede domain-search "my-domain.com";
prepend domain-name-servers 9.9.9.9;

This example specifies my-domain.com as the domain suffix, overriding whatever value is sent by the DCHP server. The value of the domain-search setting is appended to hostnames to form fully-qualified domain names. For example, if you try to connect to hostname foo (without a domain name), the DNS will be queried for the FQDN foo.my-domain.com.

The example also sets the domain name server 9.9.9.9 to take priority over domain name servers provided by a DHCP server.

Adopting the kibi

In dial-up Internet days we surmised that a 56K modem transferred data at 56 000 bits per second (or 56 kbps). Dividing this speed by 10 gave a result of 5.6 kilobytes per second (or 5.6 kBps). Thus, it was easy to calculate download times for files with the following formula, given that their sizes were expressed in kB.

download time in seconds = file size in kB / modem speed in kBps

This convention was used mostly on bulletin board systems, where users were obsessed with how fast they could download files. It was also common to distinguish between 1000 bytes (1 kB) and 1024 bytes (KB) in writing by using different cases of the letter k.

I continued to use this convention until recently when a reddit post made me aware of IEC 60027-2. This standard, set in 1999, introduced the kibi and disambiguated the units used for expressing data sizes. The blog post ‘The MB Confusion’ describes the standard better than the related Wikipedia article, and explains why its adoption is slow.

Trap with `gmtime` and other time functions

From man gmtime:

The gmtime() function converts the calendar time timep to broken-down time representation, expressed in Coordinated Universal Time (UTC). It may return NULL when the year does not fit into an integer. The return value points to a statically allocated struct which might be overwritten by subsequent calls to any of the date and time functions. The gmtime_r() function does the same, but stores the data in a user-supplied struct.