in Embedded, IOT, Linux

Embedded Linux S3C2440 – Kernel Module

Summary

Embedded Linux device driver is part of Linux kernel, application will use Linux device driver to interact with Linux Kernel. You can compile the driver into the kernel or you can compile the driver into module, load and use the module.

Application programs have a main() function, where Linux drivers have macro module_init to insert the driver’s initialization routine into kernel’s list of global initialization routines, when kernel initialized, the driver will be initialized. Macro module_exit will unregister driver when driver exits.

Applications linked against the C library, the drivers modules do not link to standard C libraries, so they cannot call standard C functions.

Each module defines a version symbol called __module_kernel_version, and for device management, the kernel uses a pair of major and minor numbers to identify a device. Same device driver use the same major number, different instances of device will use different minor number.

Device driver interface

Device driver interface is in the file_operation() data structure, defined in the file /home/iot/mini2440/linux-3.8.7/include/linux/fs.h

struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
        int (*readdir) (struct file *, void *, filldir_t);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
        int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

struct file is also defined in .

Configure Kernel to support Module and Kernel Debugging

[root@localhost linux-3.8.7]# pwd
/home/iot/mini2440/linux-3.8.7
[root@localhost linux-3.8.7]# make menuconfig

Select and enter “Enable loadable module support”, as below,

[*] Enable loadable module support

Select below items:

--- Enable loadable module support
[*]   Module unloading
[*]     Forced module unloading
[*]   Module versioning support
[*]   Source checksum for all modules
[ ]   Module signature verification

Exit to the main menu, and enter into “Kernel hacking”,

[*] Compile the kernel with debug info
[*] KGDB: kernel debugger  --->

Enter into “KGDB: kernel debugger”,

--- KGDB: kernel debugger
<*>   KGDB: use kgdb over the serial console (NEW)
[*]   KGDB: internal test suite

Exit and save.

Create the Driver Module and add to the source tree

[root@localhost char]# pwd
/home/iot/mini2440/linux-3.8.7/drivers/char
[root@localhost char]# vim foo.c

The source code of foo.c is as below,

#ifndef __KERNEL_
#define __KERNEL_
#endif

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>       /* printk() */
#include <linux/slab.h>         /* kmalloc() */
#include <linux/fs.h>           /* file system */
#include <linux/errno.h>
#include <linux/types.h>        /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h>        /* O_ACCMODE */
#include <linux/ioctl.h>
#include <linux/uaccess.h>      /* COPY_TO_USER */
#include <asm/system.h>         /* cli(), *_flags */

#define DEVICE_NAME     "foo"
#define FOO_MAJOR       229
#define FOO_MINOR       0
#define MAX_BUF_LEN 1024

char drv_buf[MAX_BUF_LEN];
int WRT_LEN = 0;

/* reverse data in buffer */
void do_write(void)
{
        int i;
        int len = WRT_LEN;
        char temp;
        for(i = 0; i<(len>>1); i++, len--)
        {
                temp = drv_buf[len-1];
                drv_buf[len-1] = drv_buf[i];
                drv_buf[i] = temp;
        }

}

ssize_t foo_write(struct file *filp, const char *buffer, size_t count, loff_t *f_pos)
{
        if(count > MAX_BUF_LEN)
                count = MAX_BUF_LEN;

        copy_from_user(drv_buf, buffer, count);
        WRT_LEN = count;
        printk("user write data to driver.\n");
        do_write();
        return count;
}

/********************************************************************/
ssize_t foo_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
        if(count >MAX_BUF_LEN)
                count = MAX_BUF_LEN;
        copy_to_user(buffer, drv_buf, count);
        printk("user read data from driver.\n");
        return count;
}

/**********************************************************************/
long foo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
        switch(cmd)
        {
                case 1:
                        printk("running ioctl command 1\n");
                        break;
                case 2:
                        printk("running ioctl command 2\n");
                        break;
                default:
                        printk("Error ioctl command.\n");
                        break;
        }

        return 0;
}

/***********************************************************************/
int foo_open(struct inode *inode, struct file *filp)
{
        sprintf(drv_buf, "device open success!.\n");
        printk("device open success!.\n");
        return 0;
}

/************************************************************************/
int foo_release(struct inode *inode, struct file *filp)
{
        printk("Device release.\n");
        return 0;
}

/***********************************************************************/
static struct file_operations foo_fops =
{
        .write  = foo_write,
        .read   = foo_read,
        .unlocked_ioctl = foo_ioctl,
        .open   = foo_open,
        .release=foo_release,
};


/***************************************************************************/

static int __init foo_init(void)
{

        int result;

        result = register_chrdev(FOO_MAJOR, "scull", &foo_fops);
        if(result<0)
                return result;

        printk("%s initialized.\n", DEVICE_NAME);
        return 0;
}

static void __exit foo_exit(void)
{
        unregister_chrdev(FOO_MAJOR, "foo");
}


module_init(foo_init);
module_exit(foo_exit);


MODULE_LICENSE("GPL");

Edit the Makefile under the character driver directory,

[root@localhost char]# pwd
/home/iot/mini2440/linux-3.8.7/drivers/char
[root@localhost char]# vim Makefile

Add below line,

obj-m                           += foo.o

Configure and compile Busybox

We need to include lsmod (list module), insmod (install module), mknod (create device file) and rmmod (uninstall module) into Busybox.
Start Busybox configuration editor.

[root@localhost busybox-1.19.4]# pwd
/home/iot/mini2440/busybox-1.19.4
[root@localhost busybox-1.19.4]# make menuconfig

Enter “Linux Module Utilities”, and select below items,

[*]   insmod
[*]   rmmod
[*]   lsmod
[*]   modprobe
[*]   depmod

Exit to main and enter “Coreutils”, select mknod,

 [*] mknod

Save and exit.

Compile and install Busybox,

[root@localhost busybox-1.19.4]# make clean
[root@localhost busybox-1.19.4]# make
[root@localhost busybox-1.19.4]# make install

Generate initramfs

Generate new rootfs, and include the newly compiled Busybox binary into rootfs,

[root@localhost rootfilesystem]# pwd
/home/iot/mini2440/rootfilesystem
[root@localhost rootfilesystem]# ./create_rootfs_bash.sh
------Create rootfs --------
/home/iot/mini2440/rootfilesystem/rootfs /home/iot/mini2440/rootfilesystem
--------Create root,dev....----------
---------Copy from busybox, rootfs-base, libs -----------
---------make node dev/console dev/null-----------------
mknod: ‘/dev/ptmx’: File exists
14092 blocks
/home/iot/mini2440/rootfilesystem
[root@localhost rootfilesystem]#
[root@localhost rootfilesystem]# ll initramfs.cpio
-rw-r--r--. 1 root root 7215104 May  8 13:17 initramfs.cpio

Compile Linux Kernel and Module Foo

After generate initramfs.cpio, now can build the kernel and newly defined module Foo.

[root@localhost linux-3.8.7]# pwd
/home/iot/mini2440/linux-3.8.7
[root@localhost linux-3.8.7]# make clean
[root@localhost linux-3.8.7]# make
[root@localhost linux-3.8.7]# ll ./arch/arm/boot/zImage
-rwxr-xr-x. 1 root root 5496672 May  8 13:26 ./arch/arm/boot/zImage

Compile the module,

[root@localhost linux-3.8.7]# make modules
  CHK     include/generated/uapi/linux/version.h
  CHK     include/generated/utsrelease.h
make[1]: 'include/generated/mach-types.h' is up to date.
  CALL    scripts/checksyscalls.sh
  Building modules, stage 2.
  MODPOST 2 modules
[root@localhost linux-3.8.7]# ls -l drivers/char/foo.ko
-rw-r--r--. 1 root root 69081 May  8 13:26 drivers/char/foo.ko
[root@localhost linux-3.8.7]#

Copy foo.ko to TFTP /var/lib/tftpboot directory, will transfer to S3C2440 board later.

[root@localhost linux-3.8.7]# cp /home/iot/mini2440/linux-3.8.7/drivers/char/foo.ko /var/lib/tftpboot/

Download new zImage to S3C2440 board

From minicom:

##### FriendlyARM BIOS 2.0 for 2440 #####
[x] format NAND FLASH for Linux
[v] Download vivi 
[k] Download linux kernel 
[y] Download root_yaffs image 
[a] Absolute User Application
[n] Download Nboot for WinCE 
[l] Download WinCE boot-logo
[w] Download WinCE NK.bin 
[d] Download & Run 
[z] Download zImage into RAM 
[g] Boot linux from RAM 
[f] Format the nand flash 
[b] Boot the system 
[s] Set the boot parameters 
[u] Backup NAND Flash to HOST through USB(upload) 
[r] Restore NAND Flash from HOST through USB 
[q] Goto shell of vivi 
[i] Version: 1026-2K
Enter your selection: k
USB host is connected. Waiting a download.

Now, Downloading [ADDRESS:30000000h,TOTAL:5496682]
RECEIVED FILE SIZE: 5496682 (18KB/S, 286S)
Downloaded file at 0x30000000, size = 5496672 bytes
Found block size = 0x00540000
Erasing...    ... done
Writing...    ... done
Written 5496672 bytes

From host,

[root@localhost mini2440]# ./download_image.sh
csum = 0x8e86
send_file: addr = 0x33f80000, len = 0x0053df60

Key in “b” to boot up S3C2440 board, and use telnet to login,

[root@localhost ~]# telnet 192.168.0.11
Trying 192.168.0.11...
Connected to 192.168.0.11.
Escape character is '^]'.

mini2440 login: root
[root@mini2440 /root]#
[root@mini2440 /root]# ifconfig
eth0      Link encap:Ethernet  HWaddr 08:90:90:90:90:90
          inet addr:192.168.0.11  Bcast:192.168.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:60 errors:0 dropped:0 overruns:0 frame:0
          TX packets:31 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:5921 (5.7 KiB)  TX bytes:2279 (2.2 KiB)
          Interrupt:51 Base address:0xc300

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

Load the Foo module

Transfer the foo.ko from host to S3C2440 board and load it into the kernel.

Start the TFTP service on the host,

[root@localhost ~]# systemctl start tftp.socket
[root@localhost ~]# systemctl start tftp.service

Use TFTP on the S3C2440 board to transfer foo.ko from host,

[root@mini2440 /sbin]# pwd
/sbin
[root@mini2440 /sbin]# tftp -g -r foo.ko 192.168.0.1
foo.ko               100% |********************************************************************************************************************************************| 69081   0:00:00 ETA
[root@mini2440 /sbin]# ls foo.ko
foo.ko
[root@mini2440 /sbin]# ls -l foo.ko
-rw-r--r--    1 root     root         69081 Jan  1 00:25 foo.ko
[root@mini2440 /sbin]# chmod +x foo.ko
[root@mini2440 /sbin]# ls -l foo.ko
-rwxr-xr-x    1 root     root         69081 Jan  1 00:25 foo.ko
[root@mini2440 /sbin]#

We will do Insert the foo.ko module, list all the modules in the system, and uninstall module foo.ko test as below,

[root@mini2440 /sbin]# insmod foo.ko
[root@mini2440 /sbin]# lsmod
    Not tainted
foo 2834 0 - Live 0xbf000000
[root@mini2440 /sbin]# rmmod foo.ko
[root@mini2440 /sbin]# lsmod
    Not tainted
[root@mini2440 /sbin]# insmod foo.ko
[root@mini2440 /sbin]# lsmod
    Not tainted
foo 2834 0 - Live 0xbf004000
[root@mini2440 /sbin]#

Create user application to test the foo.ko module

Open the application source code,

[root@localhost moduletest]# pwd
/home/iot/mini2440/myapp/moduletest
[root@localhost moduletest]# vim footest.c

The application write string “Hello, there” into kernel module, Kernel module will use void do_write(void) to reverse the string, then application will read out and print the new string “ereht, olleH”.

#include <stdio .h>
#include <unistd .h>
#include <string .h>
#include <fcntl .h>

#define DEV_NAME "/dev/foo"

int main()
{
        int fd;
        unsigned char buf[100] = "Hello, there";

        fd = open(DEV_NAME,O_RDWR);
        if(fd==-1)
        {
                printf("Cannot open device %s.\n",DEV_NAME);
                return -1;
        }

        write(fd, buf, strlen(buf));
        printf("write %s.\n",buf);
        read(fd, buf, sizeof(buf));
        printf("read %s.\n",buf);

        return 0;
}

The Makefile is as below,

BROUTPUT = /home/iot/mini2440/buildroot-2013.02/output
INCPATH = -I$(BROUTPUT)/staging/usr/include -I$(BROUTPUT)/staging/include
CC = arm-linux-gcc
LDFLAGS = -L$(BROUTPUT)/target/lib

OBS = footest.o

footest: $(OBS)
        $(CC) $(LDFLAGS) $(OBS) -o footest
$(OBS):footest.c
        $(CC) $(LDFLAGS) -c footest.c

Compile footest.c

[root@localhost moduletest]# make
arm-linux-gcc  -L/home/iot/mini2440/buildroot-2013.02/output/target/lib -c footest.c
arm-linux-gcc  -L/home/iot/mini2440/buildroot-2013.02/output/target/lib footest.o  -o footest
[root@localhost moduletest]# ll
total 20
-rwxr-xr-x. 1 root root 6122 May  8 15:46 footest
-rw-r--r--. 1 root root  411 May  8 15:29 footest.c
-rw-r--r--. 1 root root 1572 May  8 15:46 footest.o
-rw-r--r--. 1 root root  308 May  8 15:45 Makefile

Reduce the size of footest, strip away the debugging symbols, we found the size almost was reduced to half after strip.

[root@localhost moduletest]# arm-linux-strip footest
[root@localhost moduletest]# ll footest
-rwxr-xr-x. 1 root root 3424 May  8 15:50 footest
[root@localhost moduletest]#
[root@localhost moduletest]# cp ./footest /var/lib/tftpboot/
[root@localhost moduletest]#

Now test the application footest,

[root@mini2440 /sbin]# tftp -g -r footest 192.168.0.1
footest              100% |********************************************************************************************************************************************|  6122   0:00:00 ETA
[root@mini2440 /sbin]# chmod 777 footest
[root@mini2440 /sbin]# lsmod
    Not tainted
foo 2834 0 - Live 0xbf004000
[root@mini2440 /sbin]# mknod /dev/foo c 229 0
[root@mini2440 /sbin]# ls -l /dev/foo
crw-r--r--    1 root     root      229,   0 Jan  1 01:50 /dev/foo
[root@mini2440 /sbin]# ./footest
write Hello, there.
read ereho, tlleHsuccess!.
.
[root@mini2440 /sbin]# ./footest
write Hello, there.
read ereho, tlleHsuccess!.
.
[root@mini2440 /sbin]# ./footest
write Hello, there.
read ereho, tlleHsuccess!.
.

The output from application footest interleaves with the output of kernel module.

Reference

Linux Kernel: How does copy_to_user work