Detailed explanation of driver framework functions

The framework of the article is based on the previous chapter

https://blog.csdn.net/qq_52749711/article/details/132409329
are almost the same. The names here have been changed, but they remain true to their origins.

module_init entry function

Like the main function in user mode, it calls the function inside the brackets to execute, and the function inside is the entry function.
For example:

static int  pin5_drv_init(void);//函数声明
module_init(pin5_drv_init)

The function contains code such as registration driver

module_exit exit function

Functions that need to be executed after running, the principle is the same as above

module_exit(pin5_drv_exit);

The function contains code for uninstalling the driver and so on.

register_chrdev

register_chrdevfunction is one of the functions in the Linux kernel used to register character device drivers. A character device is a device that interacts with character streams, such as terminals, serial ports, etc. In the Linux kernel, the character device driver implements operations such as reading and writing character devices through file operation functions.

The function is usually declared as follows:

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

Parameter Description:

major: Specify the major device number. For character devices, the major device number is the number that uniquely identifies a device driver. The major device number can MAJOR(dev_t dev)be obtained through a macro.
name: Specify the name of the device, used to display the name of the device in /proc/devices.
fops: file_operationsPointer to a structure that contains the operating functions of the character device driver, such as reading, writing, opening, closing, etc.

return value:

On successful registration, the assigned major device number is returned.
When registration fails, a negative value is returned, usually an error code.
After using register_chrdevthe function to register the character device driver, you need to call this function in the module's initialization function. For example:

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

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // Open operation implementation
    return 0;
}

static int my_chardev_release(struct inode *inode, struct file *file)
{
    
    
    // Release operation implementation
    return 0;
}

static struct file_operations my_fops = {
    
    
    .open = my_chardev_open,
    .release = my_chardev_release,
    // Other operation implementations
};

static int __init my_chardev_init(void)
{
    
    
    int major = register_chrdev(0, "my_chardev", &my_fops);
    if (major < 0) {
    
    
        printk(KERN_ALERT "Failed to register char device\n");
        return major;
    }
    printk(KERN_INFO "Registered char device with major number %d\n", major);
    return 0;
}

static void __exit my_chardev_exit(void)
{
    
    
    unregister_chrdev(major, "my_chardev");
    printk(KERN_INFO "Unregistered char device\n");
}

module_init(my_chardev_init);
module_exit(my_chardev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Character Device Driver");

my_chardev_open (open function)

my_chardev_openIt is a function in the character device driver, used to handle the opening operation of the device. When registering a character device, you need to struct file_operationsset a pointer to your own implemented open function in the structure. The kernel will call this function when the device is opened.

This is the definition of my_chardev_open function:

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // Open operation implementation
    return 0;
}

In this function, you can perform tasks related to the device opening operation, such as initializing the device state, allocating resources, recording the number of times the device is opened, etc. This function will receive two parameters:

struct inode *inode: This is a pointer to the index node (inode) of the file and contains metadata information about the file. When the device is opened, the kernel passes the relevant inode to the open function.

  • struct inode *inode:

  • inode(Index node) contains the metadata information of the file, such as the file's permissions, size, user, etc. It uniquely identifies a file in the file system.
    You can use i_modethe fields to get the file's permission information, and the i_uidand i_gidfields to get the file's user and group IDs.

  • i_privateFields can be used to store private data related to the device. You can set this field during driver initialization.

struct file *file: This is a pointer to a data structure representing the file. It contains information related to file operations, such as access mode, file location, etc.

  • struct file *file:

  • The file structure contains information related to opening the file, such as file location, access mode, etc.

  • The f_pos field indicates the current position offset of the file.

  • The f_flags field contains the flags used when opening the file, such as read, write, append, etc.

  • The f_mode field contains the access mode when opening the file, which can be determined through bit operations.

  • The private_data field can be used to store private data related to file operations, and you can set it when opening the file.

The return value of a function is an integer, usually used to indicate whether the operation was successful. If the open operation is successful, it is customary to return 0, indicating that no error occurred. If an error occurs, a negative value can be returned, corresponding to a different error code.

Here is an example showing how to my_chardev_openimplement the open operation in a function:

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // Perform device-specific tasks during open, if any
    printk(KERN_INFO "Device opened\n");
    
    // Increment the device's open count (if you want to track it)
    try_module_get(THIS_MODULE);
    
    return 0; // Return 0 to indicate success
}

In the above example, we use printka function to output a log indicating that the device has been opened. If you wish to track the number of times a device has been opened, you can try_module_get(THIS_MODULE)increment the kernel module's reference count using . module_put(THIS_MODULE)This way you can use decrement the reference count when the device is shut down .

In short, my_chardev_openthe function allows you to perform some operations when the character device is opened. You can write appropriate opening operation code according to the characteristics and needs of the device.

static int my_chardev_open(struct inode *inode, struct file *file)
{
    
    
    // 访问 inode 信息
    printk(KERN_INFO "文件权限: %o\n", inode->i_mode & 0777);
    printk(KERN_INFO "文件所有者用户ID: %d\n", inode->i_uid.val);
    printk(KERN_INFO "文件所有者组ID: %d\n", inode->i_gid.val);

    // 访问文件信息
    printk(KERN_INFO "文件位置: %lld\n", file->f_pos);
    printk(KERN_INFO "文件标志: %x\n", file->f_flags);

    // 在文件结构中设置 private_data
    file->private_data = /* 在此处添加你的私有数据 */;

    return 0;
}

write function

The function prototype pin5_writelooks like the write operation function in the character device driver, which is used to write data from user space to the device. Let me explain what the parameters of this function mean:

file: This is a struct file pointer representing an open file and it contains information related to the open file. This parameter specifies the file to be written.
buf: This is a pointer to a user-space buffer containing the data to be written to the device. __user is a flag indicating that this is user space data and therefore needs to be handled with care in kernel space.
count: This is the number of bytes to write, specifying the length of data in the buffer.
ppos: This is a pointer to loff_t type, indicating the current position offset of the file. During a write operation, the kernel may need to update this location.

The return value of the function is of type ssize_t, indicating the number of bytes written, or if an error occurs, a negative value is returned, corresponding to a different error code.
Here is a simplified implementation of an example pin5_write function to illustrate how it works:

static ssize_t pin5_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)
{
    
    
        printk("pin5_write\n");
        return 0;
}

In actual driver development, you need to implement the pin5_write function according to the characteristics and requirements of the device. For example, you can add operations such as writing data to the device and updating file offsets as needed. At the same time, ensure proper data copying and verification between kernel space and user space to ensure security and stability.

#include <linux/fs.h>
#include <linux/uaccess.h>

// 假设你的设备在打开时已经被初始化为pin5设备

static ssize_t pin5_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    
    
    ssize_t written = 0;

    // 验证用户空间内存并将数据从用户空间复制到内核空间
    if (!access_ok(VERIFY_READ, buf, count))
        return -EFAULT;

    // 在内核中分配一个临时缓冲区
    char *kernel_buf = kmalloc(count, GFP_KERNEL);
    if (!kernel_buf)
        return -ENOMEM;

    // 从用户空间复制数据到内核缓冲区
    if (copy_from_user(kernel_buf, buf, count)) {
    
    
        kfree(kernel_buf);
        return -EFAULT;
    }

    // 在这里执行将数据写入设备的操作,示例中省略

    // 记录写入的字节数
    written = count;

    // 释放临时分配的内存
    kfree(kernel_buf);

    // 更新文件位置偏移
    *ppos += written;

    return written;
}

In the above code, we first verify whether the user space memory is accessible and use access_okfunctions to achieve this. We then allocated a temporary buffer in kernel space and used copy_from_usera function to copy the data from user space to kernel space.

The next part should be the actual operation of writing data from the kernel buffer to the device. This may involve the operation of device registers, data transmission, etc., which can be implemented based on the characteristics of your device.

Finally, we record the number of bytes written and free the temporarily allocated memory. Also, the file position offset has been updated to maintain the correct file position after write operations.

It should be noted that in actual driver development, you need to handle it appropriately according to the characteristics of the device, kernel version and requirements. It is very important to ensure the correctness and security of data and the reasonable management of memory.

class_create

The class_create function is a function in the Linux kernel used to create device classes. Device classes are a way of organizing and managing devices that allow related devices to be grouped together and provide common properties and operations for these devices. This is useful for the management of character devices, block devices, etc.

struct class *class_create(struct module *owner, const char *name);

Parameter Description:

owner: Specifies the kernel module that owns this class. Generally, you can set it to THIS_MODULE, which means that the module that created the class is the current module.
name: Specify the name of the device class. This name will be displayed in the /sys/class directory and is used to identify the device class.
return value:

On success, returns a pointer to the newly created device class.
On failure, an error pointer is returned.
After calling class_createthe function, you can register relevant devices into this class, and then the kernel will create corresponding device files for these devices and /sys/classcreate corresponding directories under the directory.

Here is an example showing how to use the class_create function:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

static struct class *my_class;

static int __init my_module_init(void)
{
    
    
    my_class = class_create(THIS_MODULE, "my_device_class");
    if (IS_ERR(my_class)) {
    
    
        printk(KERN_ALERT "Failed to create class\n");
        return PTR_ERR(my_class);
    }

    // Create and register devices here

    printk(KERN_INFO "Module initialized\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    
    
    class_destroy(my_class);
    printk(KERN_INFO "Module exited\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Device Class Module");

In the above example, class_createa device class named is first created using a function "my_device_class". You can then add relevant devices to this class by creating and registering them in the module's initialization function. When the module exits, use class_destroythe function to destroy the device class.

It should be noted that class_create and related device operation functions are used for kernel module development. If you are developing a character device driver or other type of kernel module, you can use these functions to manage device classes and devices as needed.

device_create

device_createfunction is a function used to create a device in the Linux kernel. It /sys/class/creates a subdirectory for device classes and creates a device file in that subdirectory. This function is typically class_createused in conjunction with functions to associate a device with a specific device class.

This is device_createthe prototype of the function:

struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

Parameter Description:

class: Specify the device class to which the device belongs. This parameter is usually the device class pointer returned by the class_create function.
parent: Specifies the parent device of the device. Generally, it can be set to NULL.
devt: Specify the device number of the device. You can use the MKDEV (major, minor) macro to create the device number.
drvdata:Specifies the private data pointer of the device.
fmt: Specify the format of the device name, used to create subdirectories and device files under /sys/class/.

return value:

On success, returns a pointer to the newly created device.
On failure, an error pointer is returned.
Here is an example showing how to use the device_create function:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>

static struct class *my_class;
static struct device *my_device;

static int __init my_module_init(void)
{
    
    
    my_class = class_create(THIS_MODULE, "my_device_class");
    if (IS_ERR(my_class)) {
    
    
        printk(KERN_ALERT "Failed to create class\n");
        return PTR_ERR(my_class);
    }

    // Create and register devices here

    my_device = device_create(my_class, NULL, MKDEV(0, 0), NULL, "my_device");
    if (IS_ERR(my_device)) {
    
    
        printk(KERN_ALERT "Failed to create device\n");
        class_destroy(my_class);
        return PTR_ERR(my_device);
    }

    printk(KERN_INFO "Module initialized\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    
    
    device_destroy(my_class, MKDEV(0, 0));
    class_destroy(my_class);
    printk(KERN_INFO "Module exited\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample Device Module");

In the above example, we first class_createcreate a device class using functions. Then, device_createa device named under that class was created using functions "my_device". On module exit, we use device_destroya function to destroy the device, and then class_destroya function to destroy the device class.

It should be noted that device_createthe related device operation functions are used for kernel module development. If you are developing a character device driver or other type of kernel module, you can use these functions to create and manage devices as needed.

ioremap

In Linux kernel development, ioremapfunctions are used to map physical addresses to kernel space so that the kernel can directly access hardware registers, peripheral memory, etc. at these addresses. This is because accessing hardware registers usually requires the use of special I/O instructions, and these instructions can only be executed in kernel mode.

ioremapThe prototype of the function is as follows:

void __iomem *ioremap(resource_size_t phys_addr, size_t size);

Parameter Description:

phys_addr: The physical address to be mapped.
size: The size of the map, in bytes.
return value:

On success, returns a pointer to the mapped area.
On failure, a null pointer is returned.
Before using ioremapthe function, you need to make sure that your physical address is valid and not used by other parties. After mapping, you can use pointers in the kernel to access the contents of the mapped area, just like accessing ordinary memory.

The following is an example showing how to use the ioremap function to access hardware registers

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>

static void __iomem *hw_regs;

static int __init my_module_init(void)
{
    
    
    // 为物理地址0x12345678映射一个大小为4字节的映射区域
    hw_regs = ioremap(0x12345678, 4);
    if (!hw_regs) {
    
    
        printk(KERN_ALERT "Failed to remap hardware registers\n");
        return -ENOMEM;
    }

    // 使用映射后的指针来访问寄存器
    unsigned int reg_value = readl(hw_regs);
    printk(KERN_INFO "Hardware register value: %u\n", reg_value);

    printk(KERN_INFO "Module initialized\n");
    return 0;
}

static void __exit my_module_exit(void)
{
    
    
    // 取消映射
    iounmap(hw_regs);

    printk(KERN_INFO "Module exited\n");
}

module_init(my_module_init);
module_exit(my_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Sample I/O Remapping Module");

In the above example, we first use ioremapa function to 0x12345678map the physical address to the kernel space, and then use readlthe function to read the value of the hardware register from the mapped area. iounmapUse functions to unmap when the module exits .

It should be noted that ioremap and related functions are generally used for low-level hardware programming and need to be used with caution to ensure that you follow the specifications and requirements of the hardware device when accessing the mapped area.

MODULE_LICENSE(“GPL”);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“Your Name”);
MODULE_DESCRIPTION(“Sample I/O Remapping Module”);

MODULE_LICENSE("GPL");:
This macro is used to specify the license type of the kernel module. In Linux kernel development, it is important to follow different license types. "GPL" stands for GNU General Public License, which is an open source software license that requires derived code to also comply with the GPL. If the module uses a different license, you can specify other license types here, such as "LGPL", "MIT", etc.

MODULE_AUTHOR("Your Name");:
This macro is used to specify the author information of the kernel module. You should replace "Your Name" with your real name or logo.

MODULE_DESCRIPTION("Sample I/O Remapping Module");:
This macro is used to provide a brief description of the kernel module. Here you can describe the function, purpose or features of the module. A module description is usually displayed when the module is loaded.

These macros provide a standardized way to record and display information about kernel modules, helping developers understand the characteristics and background of the module. When other developers or kernel maintainers look at your code, they can easily understand the basic information about the module.

Finish

If you have any questions, you are welcome to ask them and we can make progress together.

Guess you like

Origin blog.csdn.net/qq_52749711/article/details/132431438