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
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:
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
Step 3 Adding new module in Driver
Once the module is written with all the basic necessary source file, Kconfig and Makefile.
- 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.
source "drivers/invstr/Kconfig"
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.
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
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.shThen run below command for building kernel for emulatore
BUILD_CONFIG=common-modules/virtual-device/build.config.virtual_device.x86_64build/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]
ThanksSaurabhHappy Coding!!!