Saturday, 22 July 2023

Build a Custom Kernel Module for Android


Hi Guys!!!Hope you are doing well !!!.

Today I will describe how you can write a custom kernel module(Hello world) for Android and load it as separate node or driver.
A kernel module can be a device driver that handles or manages a hardware. 

This post I am writing  mainly for creating invstr device drive for my previous post.
My previous 4 post I have given details on HAL and Kernel as bellow 
You can also write Hello World as device driver and load it in kernel. Running it on Emulator you can see in 
adb shell and cd dev . Under it you can find helloworld

Loadable Kernel Module
Android 8.0 introduced Loadable Kernel Module, all system-on-chip (SoC) kernels must support loadable kernel modules.

Step 1 Create Module

Going to show step by step guide to create and load invstr device driver in kernel(common-android13-5.15) for Android 13.

Create a folder invstr under common/driver/invstr
Change Overview:






Step 2 Module implementation

invstr.c file : implementation  (i/p abc o/p cba)

invstr_init: register a character device at /dev/invstr

What is character device driver ? Type of character device driver?
I will write separate blog.

invstr_exit:
remove the registered device

invstr_receive: aka read, copy from an internal buffer to the user buffer
  • Use f_pos to know how many bytes have been read
  • Return the number of bytes for current read command
  • Return 0 to indicate end of data
invstr_send: aka write, copy from the user buffer to the internal buffer
  • Use f_pos to know how many bytes have been written
  • Return the number of bytes for current write command
  • System write expects that total returned bytes must be equal to the total requested bytes, therefore
#include <linux/module.h> // all kernel modules
#include <linux/kernel.h> // KERN_INFO
#include <linux/errno.h> // EFAULT
#include <linux/device.h> // device register
#include <linux/fs.h> // file_operations
#include <linux/types.h> // size_t
#include <linux/uaccess.h> // copy_from/to_user

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Saurabh Sharma");
MODULE_DESCRIPTION("Inverse String");

/* DEVICE NAME */
#define DEVICE_NAME "invstr" // The device will appear at /dev/invstr
#define CLASS_NAME "invstr"
#define DEVICE_DEBUG "invstr: "

/* Global variable */
static int majorNumber = 0;
static struct class* invstrClass = NULL;
static struct device* invstrDevice = NULL;

#define MAX_SIZE 1024
static char __buffer[MAX_SIZE] = {0};

/* Function declaration */
static int invstr_init(void);
static void invstr_exit(void);
static ssize_t invstr_receive(
struct file *filp, char *buf, size_t count, loff_t *f_pos
);
static ssize_t invstr_send(
struct file *filp, const char *buf, size_t count, loff_t *f_pos
);

/* Device operations */
static struct file_operations __fops =
{
.owner = THIS_MODULE,
.read = invstr_receive,
.write = invstr_send,
};

static int invstr_init(void){
// Try to dynamically allocate a major number for the device -- more difficult but worth it
majorNumber = register_chrdev(0, DEVICE_NAME, &__fops);
if (majorNumber<0){
printk(KERN_ERR DEVICE_DEBUG "Failed to register a major number\n");
return majorNumber;
}
printk(KERN_INFO DEVICE_DEBUG "Registered with major number %d\n", majorNumber);

// Register the device class
invstrClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(invstrClass)) // Check for error and clean up if there is
{
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ERR DEVICE_DEBUG "Failed to register device class\n");
return PTR_ERR(invstrClass); // Correct way to return an error on a pointer
}
printk(KERN_INFO DEVICE_DEBUG "Device class registered correctly\n");

// Register the device driver
invstrDevice = device_create(invstrClass, NULL, MKDEV(majorNumber, 0), NULL, DEVICE_NAME);
if (IS_ERR(invstrDevice)) // Clean up if there is an error
{
class_destroy(invstrClass);
unregister_chrdev(majorNumber, DEVICE_NAME);
printk(KERN_ERR DEVICE_DEBUG "Failed to create the device\n");
return PTR_ERR(invstrDevice);
}

// clear buffer
memset(__buffer, 0, MAX_SIZE);
printk(KERN_INFO DEVICE_DEBUG "Init!\n");
return 0; // Zero means OK
}

static void invstr_exit(void){
device_destroy(invstrClass, MKDEV(majorNumber, 0)); // remove the device
class_unregister(invstrClass); // unregister the device class
class_destroy(invstrClass); // remove the device class
unregister_chrdev(majorNumber, DEVICE_NAME); // unregister the major number
printk(KERN_INFO DEVICE_DEBUG "Exit\n");
}

static ssize_t invstr_receive(
struct file *filp, char *buf, size_t count, loff_t *f_pos
) {
ssize_t remain = MAX_SIZE - *f_pos;
ssize_t len = count > remain ? remain : count;

printk(KERN_INFO DEVICE_DEBUG "Read from device, remain=%ld, *f_pos= %lld, count= %ld\n", remain, *f_pos, count);
if(remain <= 0) return 0;

if (copy_to_user(buf, __buffer+*f_pos, len)) {
printk(KERN_ERR DEVICE_DEBUG "Can not copy to user\n");
return -EFAULT;
}
printk(KERN_INFO DEVICE_DEBUG "Read from device: %s\n", __buffer);

*f_pos += len;
return len;
}

static ssize_t invstr_send(
struct file *filp, const char *buf, size_t count, loff_t *f_pos
) {
int i, temp;
ssize_t remain = MAX_SIZE - *f_pos;
ssize_t len = count > remain ? remain : count;

printk(KERN_INFO DEVICE_DEBUG "Write to device, remain=%ld, *f_pos= %lld, count= %ld\n", remain, *f_pos, count);

if(*f_pos == 0) memset(__buffer, 0, MAX_SIZE);

if(remain <= 0) return count; // ignore all requested bytes

if(copy_from_user(__buffer+*f_pos, buf, len)) {
printk(KERN_ERR DEVICE_DEBUG "Can not copy from user\n");
return -EFAULT;
}
printk(KERN_INFO DEVICE_DEBUG "Write to device: %s\n", __buffer);

for(i=*f_pos; i<*f_pos+len; i++) {
// temp variable use to temporary hold the string
temp = __buffer[i];
__buffer[i] = __buffer[*f_pos+len - i - 1];
__buffer[*f_pos+len - i - 1] = temp;
}
printk(KERN_INFO DEVICE_DEBUG "Convert to: %s\n", __buffer);

*f_pos += len;
return len;
}

module_init(invstr_init);
module_exit(invstr_exit);



kconfig file :  to configure driver loading in kernel build







Mak


Makefile: To configure build and output






Step 3 Adding new module in Driver

Once the module is written with all the basic necessary source file, Kconfig and Makefile. 
We have to add refrence to these two files in the main drivers Kconfig and Makefile
In order to do so go to the drivers directory and open the Kconfig and Makefile in the editor of your choice and follow the following steps.

a) After opening the drivers/Kconfig add the following statement in the end of the file before the endmenu line.
source "drivers/invstr/Kconfig"
b) Once the drivers/Kconfig is edited. Open the drivers/Makefile and add the following line in the end of the file.
obj-$(CONFIG_INVSTR)            += invstr/
Step 4 Enable Modules loading and unloading in the kernel config file

Till Step 3 we will done almost all thing for adding new module in kernel and building it as a driver. But if you build you will not get your module loaded in kernel image. that's why Step 4 is very important to get added your module in kernel image.

In order to check this configuration, go to the kernel source parent directory (in our example case kernel-common-android13-5.15/kernel/configs/android-5.15).

Once you are in the “configs” directory you must have to know the config file which is being used by the Android/Emulator build system to build your kernel.
In our example case it is using the file "android-base.config"

Search "CONFIG_MODULES" string in above file and add add below configuration even by searching CONFIG_MODULES does not find any thing

CONFIG_MODULES=y
CONFIG_MODULE_FORCE_LOAD=y
CONFIG_MODULE_UNLOAD=y
CONFIG_MODULE_FORCE_UNLOAD=y
Step 5 Final build for Emulator or real device:
Now at this stage we have do all the necessary setting in order to get our module working. Now it is time to build the android for your device/emulator.

First build entire kernel image using below command
BUILD_CONFIG=common-modules/virtual-device/build.config.cuttlefish.x86_64 
build/build.sh
Then run below command for building kernel for emulatore
BUILD_CONFIG=common-modules/virtual-device/build.config.virtual_device.x86_64
build/build.sh
After successful build go to out/android13-5.15/dist and then grep invstr , It will show below search  result if build was success .
./modules.builtin:336:kernel/drivers/invstr/invstr.ko
Binary file ./system_dlkm.img matches
Binary file ./modules.builtin.modinfo matches
./System.map:40957:ffffffff81dc63d0 t invstr_exit
./System.map:40958:ffffffff81dc6560 t invstr_receive
./System.map:40959:ffffffff81dc6650 t invstr_send
./System.map:141325:ffffffff83cbb3ab t __initstub__kmod_invstr__226_137_invstr_init6
./System.map:146574:ffffffff83d60f8c d __initcall__kmod_invstr__226_137_invstr_init6
./System.map:149598:ffffffff83e41c00 b invstrClass
./System.map:149599:ffffffff83e41c08 b invstrDevice
Binary file ./vmlinux matches
After this you have to build Complete AOSP and lunch Emulator to see the invstr get added as driver.

Step 6 Run Emulator and test our module:
Once the emulator is up and running, it is time to test our module. So, open another shell terminal and and do the following:

adb root
adb shell
ls -la dev/invstr

crw-------  1 root        root        235,   0 2023-07-23 22:25 invstr

demesg | grep -i "invstr"

[ 2019.398656] invstr: Write to device, remain=1024, *f_pos= 0, count= 5
[ 2019.400882] invstr: Write to device: abcd
[ 2019.400882] 
[ 2019.402283] invstr: Convert to: abcd
[ 2019.402283] 
VERBOSE | Clipboard update, host->guest, value='[ 2019.398656] invstr: Write to device, remain=1024, *f_pos= 0, count= 5
[ 2019.400882] invstr: Write to device: abcd
[ 2019.400882] 
[ 2019.402283] invstr: Convert to: abcd
[ 2019.402283] 

Thanks
Saurabh
Happy Coding!!!

Wednesday, 12 July 2023

Build Android Kernel and load your module in Emulator

Hi Guys!!!Hope you are doing well !!!. 
Today I am going to shade some light on Android kernel and module building.

In my previous 3 post I have given details on 
For above 3 post we need kernel module invstr to get build and loaded once our Emulator/device is ready.

Step 1  Download Android kernel 

Why we need to download Android Kernel separately ?
The AOSP contains only pre-built kernel binaries. When AOSP system image is build, it copies the prebuilt kernel image to the output folder. Hence we need to download and build it with our changes.

you can use android official page to download kernel  as per your choice.
I have downloaded following Android kernel . 
Android Common Kernel ---- common-android13-5.15
I have downloaded it because I am using Android 13 for my previous  3 blogs .
Note :- Different Android kernel version configured build differently

Step 2 Build the Android common kernel
Android Common Kernels is used to run on Emulator. For real hardware devices, SoC companies (MTK, Qualcomm) will provide their customized kernel and build instruction.

Android 11 introduced GKI, which separates the kernel into a Google-maintained kernel image and vendor maintained-modules, which are built separately.

List of few Android kernel branch for Emulator

Android 11
    • common-android11-5.4
Android 12
    • common-android12-5.4
    • common-android12-5.10
Android 13
    • common-android13-5.10
    • common-android13-5.15
Legacy dessert kernel branches
Legacy dessert kernels were created to guarantee that new feature development didn’t interfere with merging from the Android Common Kernel

Android 10
    • android-4.9-q
    • android-4.14-q
    • android-4.19-q
Android 11
    • android-4.14-stable
    • android-4.19-stable
Building 
Install libs for building kernel
sudo apt install libssl-dev libelf-dev
A common kernels are generic, customizable kernels and therefore don’t define a default configuration
We have to set some environment settings:
Environment variableDescriptionExample
BUILD_CONFIGBuild config file from where you initialize the build environment. The location must be defined relative to the Repo root directory. Defaults to build.config. Mandatory for common kernels.BUILD_CONFIG=common/build.config.<target>.x86_64
CCOverride compiler to be used. Falls back to the default compiler defined by build.config.CC=clang
DIST_DIRBase output directory for the kernel distribution.DIST_DIR=/path/to/my/dist
OUT_DIRBase output directory for the kernel build.OUT_DIR=/path/to/my/out
SKIP_DEFCONFIGSkip make defconfigSKIP_DEFCONFIG=1
SKIP_MRPROPERSkip make mrproperSKIP_MRPROPER=1

Build Config for Android common kernel 
I am going to build common-android13-5.15 hence using below configuration
BUILD_CONFIG=common-modules/virtual-device/build.config.cuttlefish.x86_64 
build/build.sh
Then run below command for Emulator
BUILD_CONFIG=common-modules/virtual-device/build.config.virtual_device.x86_64
build/build.sh

After build complete, check the kernel file
/home/saurabh/kernel-common-android13-5.15/out/android13-5.15/dist/bzImage

If you run file command  for this bzImages yoy will get below output



Fast re-build
By default, Kernel is always built with mrproper target which removes all generated files + config + various backup files to create a clean build.
When developing on one or a few modules, we can skip the some initial steps and start re-build immediately:
BUILD_CONFIG=common-modules/virtual-device/build.config.virtual_device.x86_64 
LTO=none 
FAST_BUILD=1 
SKIP_MRPROPER=1
SKIP_DEFCONFIG=1
build/build.sh
Step 3 Include custom kernel in AOSP Build System
Edit the kernel make file that generally lies in your device folder. For Emulator path is
device/generic/goldfish/x86_64-kernel.mk










Step 4 Build Complete AOSP and Run Emulator
After modifying the Kernel make file , you need to rebuild AOSP completely to get Kernel image replaced.
make -j8

After successful build Run the emulator with below Command
emulator -verbose -show-kernel -selinux permissive -writable-system

Once Emulator get launched you can check your kernel version.


In next post Will explain how to load module in kernel and build.

Thanks
Saurabh
Happy Coding!!!!

Build a Custom Kernel Module for Android

Hi Guys!!!Hope you are doing well !!!. Today I will describe how you can write a custom kernel module(Hello world) for Android and load it a...