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!!!

2 comments:

  1. Puja Control is the leading Hardware Development & Integration service Firm. We offer our service in foreign countries as well.

    ReplyDelete
  2. I found your comment at this thread https://stackoverflow.com/questions/72517218/android-car-package-is-unavailable-in-android-studio. Could you help me to build android.car.jar on macbook?

    ReplyDelete

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...