驱动框架函数详解

文章的框架基于上篇开讲

https://blog.csdn.net/qq_52749711/article/details/132409329
都差不多,这里的名字被改变,万变不离其宗

文章目录

  • 文章的框架基于上篇开讲
  • module_init 入口函数
  • module_exit 退出函数
  • register_chrdev
  • my_chardev_open (open函数)
  • write函数
  • class_create
  • device_create
  • ioremap
  • MODULE_LICENSE("GPL");
  • 结束

module_init 入口函数

与用户态的main函数一样,它是调用括号里面的函数来执行,里面的函数为入口函数
比如:

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

函数里面包含了,注册驱动等代码

module_exit 退出函数

运行完之后需要执行的函数,原理同上

module_exit(pin5_drv_exit);

函数包含了,卸载驱动等代码

register_chrdev

register_chrdev函数是Linux内核中用于注册字符设备驱动程序的函数之一。字符设备是一种与字符流进行交互的设备,例如终端、串口等。在Linux内核中,字符设备驱动通过文件操作函数来实现对字符设备的读取、写入等操作。

该函数的声明通常如下:

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

参数说明:

major:指定主设备号,对于字符设备来说,主设备号是唯一标识一个设备驱动程序的编号。可以通过MAJOR(dev_t dev)宏来获取主设备号。
name:指定设备的名称,用于在/proc/devices中显示设备的名字。
fops:指向file_operations结构体的指针,该结构体包含了字符设备驱动程序的操作函数,如读取、写入、打开、关闭等。

返回值:

成功注册时,返回分配的主设备号。
注册失败时,返回一个负值,通常是错误代码。
在使用register_chrdev函数注册字符设备驱动后,需要在模块的初始化函数中调用这个函数。例如:

#include 
#include 

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函数)

my_chardev_open是字符设备驱动中的一个函数,用于处理设备的打开操作。在注册字符设备时,你需要在struct file_operations结构体中设置一个指向你自己实现的open函数的指针,内核在设备被打开时会调用这个函数。

这是my_chardev_open函数的定义:

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

在这个函数中,你可以执行与设备打开操作相关的任务,比如初始化设备状态、分配资源、记录设备打开次数等。这个函数会接收两个参数:

struct inode *inode:这是一个指向文件的索引节点(inode)的指针,包含了有关文件的元数据信息。在设备打开时,内核会将相关的inode传递给打开函数。

  • struct inode *inode:

  • inode(索引节点)包含了文件的元数据信息,如文件的权限、大小、所属用户等。它在文件系统中唯一标识一个文件。
    你可以使用i_mode字段来获取文件的权限信息,使用i_uidi_gid字段来获取文件的用户和组ID。

  • i_private字段可以用于存储与设备有关的私有数据,你可以在驱动初始化时设置该字段。

struct file *file:这是一个指向表示文件的数据结构的指针。它包含了与文件操作相关的信息,如访问模式、文件位置等。

  • struct file *file:

  • file结构体包含了与打开文件相关的信息,如文件位置、访问模式等。

  • f_pos字段表示文件当前的位置偏移。

  • f_flags字段包含了打开文件时使用的标志,如读、写、追加等。

  • f_mode字段包含了打开文件时的访问模式,可以通过位操作进行判断。

  • private_data字段可以用于存储与文件操作相关的私有数据,你可以在打开文件时将其设置。

函数的返回值是一个整数,通常用于指示操作是否成功。如果打开操作成功,惯例上返回0,表示没有错误发生。如果发生错误,可以返回负值,对应不同的错误代码。

下面是一个示例,展示了如何在my_chardev_open函数中实现打开操作:

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
}

在上面的示例中,我们使用printk函数来输出一条日志,表示设备已经被打开。如果你希望跟踪设备被打开的次数,你可以使用try_module_get(THIS_MODULE)来增加内核模块的引用计数。这样,在设备关闭时,你可以使用module_put(THIS_MODULE)来减少引用计数。

总之,my_chardev_open函数允许你在字符设备被打开时执行一些操作,你可以根据设备的特性和需求来编写适当的打开操作代码。

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函数

函数原型pin5_write看起来像是字符设备驱动中的写操作函数,用于处理从用户空间写入数据到设备。让我来解释一下这个函数参数的含义:

file: 这是表示打开的文件的struct file指针,它包含与打开的文件相关的信息。这个参数指定了要写入的文件。
buf: 这是一个指向用户空间缓冲区的指针,其中包含要写入设备的数据。__user是一个标记,指示这是用户空间的数据,因此在内核空间中需要小心处理。
count: 这是要写入的字节数,指定了缓冲区中的数据长度。
ppos: 这是一个指向loff_t类型的指针,表示文件的当前位置偏移。在写入操作中,内核可能需要更新这个位置。

函数的返回值是ssize_t类型,表示写入的字节数,或者如果发生错误,则返回一个负值,对应不同的错误代码。
下面是一个示例的pin5_write函数的简化实现,用于说明其工作原理:

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

在实际的驱动开发中,你需要根据设备的特性和需求来实现pin5_write函数。例如,你可以在函数中添加将数据写入设备的操作,以及根据需要更新文件位置偏移等操作。同时,确保在内核空间和用户空间之间进行适当的数据拷贝和验证,以确保安全性和稳定性。

#include 
#include 

// 假设你的设备在打开时已经被初始化为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;
}

在上述代码中,我们首先验证用户空间的内存是否可以访问,并使用access_ok函数来实现。然后,我们在内核空间中分配了一个临时缓冲区,并使用copy_from_user函数将数据从用户空间复制到内核空间。

接下来的部分应该是将数据从内核缓冲区写入设备的实际操作。这可能涉及设备寄存器的操作、数据传输等,这里根据你的设备特性进行实现。

最后,我们记录了写入的字节数,并释放了临时分配的内存。同时,更新了文件位置偏移,以便在写入操作后维护正确的文件位置。

需要注意的是,在实际的驱动开发中,你需要根据设备的特性、内核版本和要求进行适当的处理。确保数据的正确性、安全性以及对内存的合理管理是非常重要的。

class_create

class_create函数是Linux内核中用于创建设备类(device class)的函数。设备类是一种组织和管理设备的方式,它允许将相关设备分组在一起,并为这些设备提供共同的属性和操作。这对于字符设备、块设备等的管理非常有用。

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

参数说明:

owner:指定拥有该类的内核模块。一般情况下,你可以将它设置为THIS_MODULE,表示创建类的模块就是当前模块。
name:指定设备类的名称,这个名称会显示在/sys/class目录下,用于标识设备类。
返回值:

成功时,返回指向新创建的设备类的指针。
失败时,返回一个错误指针。
在调用class_create函数后,你可以将相关设备注册到这个类中,然后内核会为这些设备创建对应的设备文件,并在/sys/class目录下创建相应的目录。

以下是一个示例,展示了如何使用class_create函数:

#include 
#include 
#include 

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");

在上述示例中,首先使用class_create函数创建了一个名为"my_device_class"的设备类。然后,你可以在模块的初始化函数中创建并注册相关设备,将它们添加到这个类中。在模块退出时,使用class_destroy函数销毁该设备类。

需要注意的是,class_create和相关的设备操作函数都是用于内核模块开发的。如果你正在开发字符设备驱动或其他类型的内核模块,可以根据需要使用这些函数来管理设备类和设备。

device_create

device_create函数是用于在Linux内核中创建设备的函数,它会在/sys/class/下创建一个设备类的子目录,并在该子目录中创建一个设备文件。这个函数通常与class_create函数一起使用,用于将设备关联到特定的设备类中。

这是device_create函数的原型:

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

参数说明:

class:指定设备所属的设备类,这个参数通常是由class_create函数返回的设备类指针。
parent:指定设备的父设备,一般情况下可以设置为NULL。
devt:指定设备的设备号,可以使用MKDEV(major, minor)宏来创建设备号。
drvdata:指定设备的私有数据指针。
fmt:指定设备名称的格式,用于在/sys/class/下创建子目录和设备文件。

返回值:

成功时,返回指向新创建的设备的指针。
失败时,返回一个错误指针。
以下是一个示例,展示了如何使用device_create函数:

#include 
#include 
#include 

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");

在上述示例中,我们首先使用class_create函数创建一个设备类。然后,使用device_create函数在该类下创建了一个名为"my_device"的设备。在模块退出时,我们使用device_destroy函数销毁该设备,然后使用class_destroy函数销毁设备类。

需要注意的是,device_create和相关的设备操作函数是用于内核模块开发的。如果你正在开发字符设备驱动或其他类型的内核模块,可以根据需要使用这些函数来创建和管理设备。

ioremap

在Linux内核开发中,ioremap函数用于将物理地址映射到内核空间,以便内核可以直接访问这些地址上的硬件寄存器、外设内存等。这是因为访问硬件寄存器时通常需要使用特殊的I/O指令,而这些指令只能在内核态下执行。

ioremap函数的原型如下:

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

参数说明:

phys_addr:要映射的物理地址。
size:映射的大小,以字节为单位。
返回值:

成功时,返回一个指向映射区域的指针。
失败时,返回一个空指针。
在使用ioremap函数之前,你需要确保你的物理地址是有效的,且没有被其他部分使用。在映射后,你可以在内核中使用指针来访问映射区域的内容,就像访问普通的内存一样。

以下是一个示例,展示了如何使用ioremap函数来访问硬件寄存器

#include 
#include 
#include 

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");

在上述示例中,我们首先使用ioremap函数将物理地址0x12345678映射到内核空间,然后使用readl函数从映射区域读取硬件寄存器的值。在模块退出时,使用iounmap函数取消映射。

需要注意的是,ioremap和相关的函数一般用于底层的硬件编程,需要谨慎使用,确保你在访问映射区域时遵循硬件设备的规格和要求。

MODULE_LICENSE(“GPL”);

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

MODULE_LICENSE("GPL");
这个宏用于指定内核模块的许可证类型。在Linux内核开发中,遵循不同的许可证类型是很重要的。“GPL"代表GNU General Public License,这是一种开源软件许可证,要求派生的代码也必须遵循GPL。如果模块使用不同的许可证,可以在这里指定其他许可证类型,如"LGPL”、"MIT"等。

MODULE_AUTHOR("Your Name");
这个宏用于指定内核模块的作者信息。你应该将"Your Name"替换为你的真实姓名或标识。

MODULE_DESCRIPTION("Sample I/O Remapping Module");
这个宏用于提供内核模块的简要描述。在这里,你可以描述模块的功能、用途或特点。模块的描述信息通常会在加载模块时显示。

这些宏提供了一种标准化的方式来记录和显示有关内核模块的信息,有助于开发人员了解模块的特性和背景。当其他开发人员或内核维护者查看你的代码时,他们可以轻松地了解模块的基本信息。

结束

如有问题欢迎提出,共同进步。

你可能感兴趣的:(全志Arm-Linux,arm开发,linux)