Driver development based on Linux: kernel module parameter passing, kernel symbol table, character device driver

Kernel module passing parameters

Kernel module:

int a , b;

When installing the kernel module: insmod demo.ko a = 100 b =10;

1. The meaning of kernel module parameters

When installing the kernel module, pass values to the variables in the kernel module. This allows our kernel module to be upwardly compatible with more complex applications and downwardly adaptable to a variety of hardware.

2. Kernel module parameter passing related API

1. Function prototype: module_param(name, type, perm)

Function: Declare variables that can be passed as parameters to the kernel module

Parameters: name: variable name

type: the numerical type of the parameter to be passed —” byte (passing char), hexint (hexadecimal int), short, int, uint, long, ulong, charp (passing character pointer), bool (0/1 y /n Y/N)

perm: file permissions. If the variable to be passed is declared through module_param, then a file named after the current variable will be generated under sys/module/current module/parameters/. The permissions of the file are the permissions specified by perm, and the file content is variable value

Note: Use modinfo to check what variables your current kernel module can pass through the command line.

2. Function prototype: MODULE_PARM_DESC(_parm, desc)

Function: Add a description of the variable to be passed. This description can also be viewed through modinfo.

Parameters: _parm: the variable name of the passed parameter

desc: added description

Note: When passing parameters to a char type variable, you need to pass the corresponding ASCII decimal form.

When passing a string to a character pointer, the string cannot have spaces. If there are spaces, the ones before the spaces will be passed to the variable as parameters, and the ones after the spaces will be considered to be an unknown variable.

Sample code

Kernel export symbol table

1. The meaning of exported symbol table

Exporting the symbol table allows different modules to access each other’s resources. For example, if module 2 wants to access the resources of module 1, it only needs to export the symbol table of module 1’s resources to module 2. At this time, module 2 can access module 1. resources

2. Related APIs for exporting symbol tables

EXPORT_SYMBOL (variable name | function name)

or

EXPORT_SYMBOL_CPL (variable name | function name)

3. Export symbol table test example

3.1 Writing code

Define demo1.c to complete the definition of the function

#include <linux/init.h>
#include <linux/module.h>
int add(int i,int j)
{
    return i + j;
}
//Generate add symbol table file
EXPORT_SYMBOL(add);
static int __init mycdev_init(void)
{
    
    return 0;
}
static void __exit mycdev_exit(void)
{


}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

Define dem2.c and complete calling the functions in demo1.c

#include <linux/init.h>
#include <linux/module.h>

extern int add(int i,int j);
static int __init mycdev_init(void)
{
    printk("The execution result of calling module 1 function is: %d",add(3,5));
    return 0;
}
static void __exit mycdev_exit(void)
{


}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

3.2 Compilation

First compile demo1.c and copy the generated symbol table file Module.symvers to the path of demo2 and then compile demo2.c.

Pass modinifo at this time View demo2.ko, showing that demo2 depends on demo1

Note : Direct copying of symbol table files is not supported in higher versions of the kernel, otherwise an undefined error will be reported during compilation.

Solution:Add the file path of the demo1 symbol table to the Makefile of demo2 3.3 Installation Process

Install demo1 first, then install demo2

3.4 Uninstall

Uninstall demo2 first, then demo1

Character device driver

1. Definition of character device driver

Character devices are devices that are accessed sequentially in the form of byte streams. The driver framework designed for byte devices is called a character device driver. Most of the devices currently on the market are character devices, such as keyboards, mice, cameras…

2. Character device driver framework

1. When a character device driver is registered in the kernel, the device number corresponding to the driver will be obtained. The device number is the unique identifier of the device. The device number is divided into a major device number and a minor device number. The major device number (high 12 bits) is used to identify a type of device. The device number (low 20 bits) is used to identify a device in this category. Device number = major device number <<20 | minor device number

2. According to the device number, a device file can be created in the file system in a certain way, which is equivalent to binding the device file to the driver through the device number.

3. After the device file is created, open() | read() | write() | close() is called in the application

4. When the above function is called, the corresponding operation application method in the driver will be called back.

5. Complete the hardware control in the driver’s operating method

3. Registration and deregistration of character device drivers

3.1 related API

Header file: #include

Register character device driver

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)

Function: Realize the registration of character device drivers, and apply for 256 device resources at one time (0-255 device numbers)

Parameters: major: major device number

>0 statically specifies the major device number

=0 Dynamically apply for the main device number

name: the registered driver name

fops: operation method structure pointer, pointing to the operation method structure variable

return value:

Failure returns error code

Success: If major>0, return to apply to get the major device number

If major=0, return 0

Operation method structure, used to save and manage the respective operation methods of the driver

struct file_operations {

int (*open) (struct inode *, struct file *);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

int (*release) (struct inode *, struct file *);

};

Unregister character device driver

void unregister_chrdev(unsigned int major, const char *name)

Function: Realize the logout of the character device driver

Parameters: major: the major device number corresponding to the driver

name: driver number filled in during registration

Return value: None

3.2 Character device driver registration example

1. Write code

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
unsigned int major;
// Encapsulate operation methods. These operation methods are called back when the application layer makes system calls.
int mycdev_open(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    return 0;
}
//Define the operation method structure object and save the encapsulated operation method
struct file_operations fops = {
    .open=mycdev_open,
    .read=mycdev_read,
    .write=mycdev_write,
    .release=mycdev_close,
};
//entry function
static int __init mycdev_init(void)
{
    //Register character device driver
    major = register_chrdev(0, "mychrdev", & amp;fops);
    if (major < 0)
    {
        printk("Character device driver registration failed\\
");
        return major;
    }
    printk("Registration of character device driver successful major=%d\\
", major);
    return 0;
}
//export function
static void __exit mycdev_exit(void)
{
    //Unregister the character device driver
    unregister_chrdev(major,"mychrdev");
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

2. Compile driver

3. Install driver kernel module

You can view /proc/devices file to determine whether the driver is registered 4. Create device files

Command to create device file:

mknod device file name and path device file type driver major device number minor device number

Example: mknod /dev/mychrdev c 240 0

Analysis: mknod: command code of Chuangjie device file

/dev/mychrdev: The path and name of the created device file

c device file type (character device file) d (block device file)

240: Major device number

0: Secondary device number 0-255 is acceptable

5. Write application code to test whether the driver can be associated

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int fd = open("/dev/mychrdev", O_RDWR);
    if (fd < 0)
    {
        printf("Failed to open device file\\
");
        return -1;
    }
    printf("Open device file successfully\\
");
    //Call read
    read(fd, buf, sizeof(buf));
    //Call write
    write(fd, buf, sizeof(buf));
    //Call close
    close(fd);
    return 0;
}

6. Phenomenon

Execute the application and the operation method in the driver is called back

4. Data transfer between user and kernel

User space and kernel space cannot directly access memory to each other. Data interaction between the two requires the use of data transfer functions.

4.1 API

Header file #include

Function prototype unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

Function: Pass data from kernel space to user space

Parameters: to: buf first address where data is saved in user space

from: buf first address where data is saved in kernel space

n: length of data transferred, in bytes

Return value: 0 is returned on success, and the number of bytes not copied is returned on failure.

Function prototype unsigned log copy_from_user(void *to, const void __user *from, unsigned long n)

Function: Transfer user space data to kernel space

Parameters: to: the first address of buf where the kernel space saves data

from: buf first address where user space saves data

n: length of data transferred, in bytes

Return value: 0 is returned on success, and the number of bytes not copied is returned on failure.

4.2 User and kernel data transfer examples

application code

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include<string.h>
int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int fd = open("/dev/mychrdev", O_RDWR);
    if (fd < 0)
    {
        printf("Failed to open device file\\
");
        return -1;
    }
    printf("Open device file successfully\\
");
    fgets(buf,sizeof(buf),stdin);//Read a string in the terminal
    buf[strlen(buf)-1]='\0';
    
    write(fd, buf, sizeof(buf));//Transfer data to the kernel
    memset(buf,0,sizeof(buf));//Clear the array
    read(fd, buf, sizeof(buf));//Transfer kernel space data to user
    printf("buf:%s\\
",buf);
    close(fd);
    return 0;
}

driver code

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include<linux/uaccess.h>
unsigned int major;
char kbuf[128]={};
// Encapsulation operation method
int mycdev_open(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    int ret;
    //Copy data to user space
    ret=copy_to_user(ubuf,kbuf,size);
    if(ret)
    {
        printk("copy_to_user filed\\
");
        return -EIO;
    }
    return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    int ret;
    //Copy data from user space to kernel space
    ret=copy_from_user(kbuf,ubuf,size);
    if(ret)
    {
        printk("copy_from_user filed\\
");
        return -EIO;
    }
    return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\\
",__FILE__,__func__,__LINE__);
    return 0;
}
//Define the operation method structure object
struct file_operations fops = {
    .open=mycdev_open,
    .read=mycdev_read,
    .write=mycdev_write,
    .release=mycdev_close,
};
static int __init mycdev_init(void)
{
    //Register character device driver
    major = register_chrdev(0, "mychrdev", & amp;fops);
    if (major < 0)
    {
        printk("Character device driver registration failed\\
");
        return major;
    }
    printk("Registration of character device driver successful major=%d\\
", major);
    return 0;
}
static void __exit mycdev_exit(void)
{
    //Unregister the character device driver
    unregister_chrdev(major,"mychrdev");
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

Phenomenon: The string entered in the terminal can be printed normally, indicating that the data transfer between user space and kernel space is successful.

5. Physical memory mapping related API

Driver control hardware needs to operate the special function registers of the hardware, but the memory of the special function registers belongs to physical memory. The driver is loaded into virtual memory. If you want to operate the hardware registers in the driver, you need to map the hardware register memory to virtual memory.

#include

Function prototype: void *ioremap(phys_addr_t paddr, unsigned long size)

Function: Map physical memory of a specified size into virtual memory

Parameters: paddr: the first address of the physical memory to be mapped

size: The size of physical memory to be mapped

Return value: Success, returns the first address of the virtual memory that is successfully mapped, failure, returns NULL

Function prototype: void iounmap(const void __iomem *addr)

Function: Unmap physical memory

Parameters: addr: the first address of the virtual memory to be canceled

Return value: None

Note: For sample code of the character device driver, you can currently view the previous article “Writing driver code to control LED lights on and off” http://t.csdnimg.cn/ZBsgG