Building generic kernels

From postmarketOS
Revision as of 21:35, 12 January 2020 by HenriDellal (talk | contribs) (Building generic kernels: add guide on how to add support for physical keyboard)
Jump to navigation Jump to search

Even if kernel sources are not provided by the vendor, it's still possible to package kernel and make the phone boot.

Find generic kernel for the device

To find a needed kernel, you have to:

  1. Find out the information about the SoC/board used in the device. One SoC may have multiple boards. For example, Spreadtrum SC7731C has 2 boards: sc7731cea and sc7731ceb.
  2. Try to search for kernels on the internet. Use different queries to find as much kernels as possible. Kernel is considered matching if it has files for your board, such as device tree source (located at arch/device_arch(e.g. arm, arm64)/boot/dts). Some downstream kernels (up to 3.10.x) may have support in board files instead. They are located at arch/device_arch/mach-soc-vendor or mach-soc-name'. .(e.g. mach-sc, mach-qcom, mach-mt6582)

Add device support

Depending on the way of describing the hardware in kernel, you should choose one of these methods

Patch vendor dts in kernel

Get dts from stock boot.img, replace the board dts with it and make a patch.

Board files

If the found kernel uses board files for your SoC, then you have to change kernel config to enable/disable support for specific hardware and patch the board file if implementation of additional devices is needed (e.g. hardware keyboard).

Adapt kernel config

This step is needed to make a proper kernel config which works for the device. You have to find detailed information about the hardware. Possible ways to do it are:

  1. Get dts from stock boot.img
  2. Follow instructions from tuning sysfs page to get contents of sysfs. It is recommended to do it on a rooted phone in Android, since dump made from recovery will probably have less details about hardware.
  3. Use strings stockboot.img > stockboot.txt to dump all possible function names and find out which kernel options should be enabled to add these functions in kernel during building.
  4. Find photos of device's internals on the web. If there are none, disassemble the device yourself or ask someone to do it.

After one of these steps write down all information you've found and use it to adjust kernel config. Run pmbootstrap kconfig edit device-codename and use built-in search (press / key) to find options related to found hardware.

There is no driver for X in the kernel. What to do?

  1. Check if driver is available in other generic kernels you have found for the device. If driver is available, check step 6 and further.
  2. Try to find this driver in other kernels that don't match your device. It will be helpful to understand how it works, though you'll most likely have to rewrite(port) it.
  3. Find datasheet to have more information for porting the driver.
  4. Find a dummy driver for the driver's group(e.g. lcd_dummy.c for LCD panel drivers) in your kernel. Read it as well as other drivers for devices similar to yours.
  5. Use drivers and datasheet to make a new driver.
  6. Patch the kernel with driver, Makefile and Kconfig files related to your drivers group to add support. Give a proper name to driver option in Kconfig.
  7. Add a line to linux-device-codename's config to make the driver option appear in menuconfig. Example: # CONFIG_FB_LCD_ST7796S_MIPI is not set
  8. Run pmbootstrap kconfig edit device-codename and enable the option. Tap / on the keyboard to search for it.

Additional information

How to add hardware keyboard support

This section is useful for porting devices those use board files. If your device uses dts, the keyboard works out of the box probably.

Getting a list of input devices and keycodes

For Android devices, you may run getevent -i to get detailed information about the input devices and related keycodes. You'll get output similar to:

add device 1: /dev/input/event2
  bus:      0019
  vendor    0001
  product   0001
  version   0100
  name:     "gpio-keys"
  location: "gpio-keys/input0"
  id:       ""
  version:  1.0.1
    KEY (0001): 0002  0003  0005  0007  0008  0009  000a  003e
                0067  0069  006a  0161  020a
  input props:
add device 2: /dev/input/event0
  bus:      0019
  vendor    0001
  product   0001
  version   0100
  name:     "sci-keypad"
  location: "sci-key/input0"
  id:       ""
  version:  1.0.1
    KEY (0001): 0004  0006  000b  003d  006c  0074  008b  009e
                00e7  020b
  input props:
add device 3: /dev/input/event1
  bus:      0019
  vendor    0000
  product   0000
  version   0000
  name:     "headset-keyboard"
  location: ""
  id:       ""
  version:  1.0.1
    KEY (0001): 00e2
  input props:

It's possible to use -l flag to replace keycodes with key names from include/uapi/linux/kpd.h. Do note that some keys may not exist. For example, the output given above has F3 and F4 keycodes, but device doesn't have these keys. If you're unsure about the relations of physical keys and keycodes, you may start getevent and press buttons one by one to understand. You should also record data from dmesg, it may help later.

Add and modify input devices in board file

The code blocks of this section are taken from Spreadtrum kernel sources, they are somewhat different for other SoCs, but not much and can be used as example

This task will be reviewed on the previous example. As can be seen, the phone has an input device for headset with one button, matrix keypad device with 9 keys (remember that F3 button doesn't exist) and GPIO keys device. The board file used as the base is arch/arm/mach-sc/board-sp7715ga.c. This board has headset and matrix keypad devices set, but the GPIO keys device is missing and matrix data doesn't fit the device. So, the steps needed are:

  1. Change matrix data to fit the device
  2. Add gpio keys device to the list of devices
  3. Create gpio keys device description

The matrix data looks like this

#define CUSTOM_KEYPAD_ROWS          (SCI_ROW0 | SCI_ROW1)
#define CUSTOM_KEYPAD_COLS          (SCI_COL0 | SCI_COL1)
#define ROWS	(2)
#define COLS	(2)

static const unsigned int board_keymap[] = {

As there should be 9 matrix keys, we assume that the matrix has 3 rows and 3 columns. So, the first step is to replace existing entries with ones related to your keys. If you have access to dmesg, then you may find out the proper row and column for every key, otherwise you'll have to guess them. After that, change amount of rows and columns, patch, build and flash the kernel. Then boot to postmarketOS and run evtest. Choose the keypad device and press buttons one by one. If the keycode doesn't fit the key, then you have to make changes in board file. The final result for the device in example is:

#define CUSTOM_KEYPAD_ROWS          (SCI_ROW0 | SCI_ROW1 | SCI_ROW2 )
#define CUSTOM_KEYPAD_COLS          (SCI_COL0 | SCI_COL1 | SCI_COL2 )
#define ROWS	(3)
#define COLS	(3)

static const unsigned int board_keymap[] = {
   KEY(0, 0, KEY_5),
   KEY(1, 0, KEY_BACK),
   KEY(2, 0, KEY_DOWN),
   KEY(0, 1, KEY_SEND),
   KEY(1, 1, KEY_0),
   KEY(0, 2, KEY_3),
   KEY(1, 2, KEY_MENU),
If you were attentive, you could notice that there are only 8 keys in the array. The reason is that the power key is handled by the Spreadtrum keyboard driver already.

The second step is to add the missing GPIO keys device. There is no template for it in this exact board file, so you have to find it. It is recommended to search for it in the same path where the board file for your device is located (arch/arm/mach-sc in this case). It doesn't mean you're limited by the kernel you're using though. It's possible to search in another kernels as well, just be sure they don't differ much. Example:

#define GPIO_KEY_HOME 172
static struct platform_device gpio_button_device;

static struct platform_device *devices[] __initdata = {
		other devices

static struct gpio_keys_button gpio_buttons[] = {
		.gpio	= GPIO_KEY_HOME,
		.ds_irqflags = IRQF_TRIGGER_LOW,
		.code = KEY_HOME,
		.desc = "Home key",
		.active_low = 1,
		.debounce_interval = 2,

static struct gpio_keys_platform_data gpio_button_data = {
	.buttons = gpio_buttons,
	.nbuttons = ARRAY_SIZE(gpio_buttons),

static struct platform_device gpio_button_device = {
	.name = "gpio-keys",
	.id = -1,
	.num_resources = 0,
	.dev = {
			.platform_data = &gpio_button_data,

The final step is to add description of all GPIO keys to the device. In this example, we define GPIOs for keys and change the contents of gpio_buttons[] and test the changes. It is recommended to use debug-shell hook for initramfs so you get access to dmesg as fast as possible. In dmesg you may find details of GPIO keys initialization. If it failed, there will be a wrong GPIO mentioned. Then, if everything's alright, run evtest again and choose GPIO keys device. Test and edit the board file till it works.