【Linux驱动】字符设备驱动模板(三)—— 初始化 / 加载字符设备

文章详细介绍了Linux内核中字符设备的注册过程,包括cdev结构体用于表示字符设备,file_operations结构体定义设备操作函数,以及cdev_init、cdev_add和cdev_del等关键API的使用。此外,还提到了手动创建设备节点和应用程序测试设备驱动的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符设备的注册分为两部分:

  • 注册设备号
  • 注册设备本身

注册设备的基本流程是先初始化要注册的设备,然后将该设备加载到内核。

一、字符设备的类型表示

1、字符设备表示 — struct cdev

在 Linux 中使用 cdev 结构体表示一个字符设备,cdev 结构体及其相关api函数在 include/linux/cdev.h 文件中,结构体的定义如下。

struct cdev {
    struct kobject kobj;
    struct module *owner;                 /* 该设备在内核中 */
    const struct file_operations *ops;    /* 字符设备文件操作函数 */
    struct list_head list;
    dev_t dev;                            /* 设备号 */
    unsigned int count;
};

static struct cdev chrdev;

2、操作集合表示 — struct file_operations

操作集合代表了我们可以对该字符设备进行哪些操作,比如读设备 read、写设备 write、关闭设备 release,我们需要做的就是完善这些函数,前人已经设计出了一套完整的框架,这个框架已经包含了我们所需的大部分函数声明,所以无需我们自己设计。

内核源码中有个 include/linux/fs.h 文件,这个文件定义了一个 file_operations 结构体,该结构体中就包含了今后要实现的函数声明。

下面是操作集合对象的声明与定义。等号左边是 file_operations 的结构体成员,等号右边是我们自己实现的操作函数。

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,  // chrdevbase_open: 待实现的函数,.open: open操作对应的成员变量
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

二、字符设备操作 API

1、字符设备初始化

创建好字符设备对象以后,就需要对其进行初始化,初始化使用的函数是 cdev_init。函数声明如下:

void cdev_init(struct cdev *cdev, const struct file_operations *fops);

cdev:字符设备指针(对象)

fops:当前字符设备文件操作集合

/* 字符设备 */
static struct cdev dev;		

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
    // .open = chrdevbase_open,   
	// .read = chrdevbase_read,
	// .write = chrdevbase_write,
	// .release = chrdevbase_release,
};    

/* 初始化字符设备 */
chrdev_led.dev.owner = THIS_MODULE;         
cdev_init(&dev, &devfops);					

2、设备加载函数

创建好字符设备以后,我们需要连同设备号,将字符设备加载到内核。字符设备加载函数声明如下:

int cdev_add(struct cdev *p, dev_t dev, unsigned count);

p:要添加的字符设备指针

dev:为当前字符设备注册的设备号

count:加载的字符设备的数量

返回值:0 代表成功;其他代表失败

static dev_t devid;                     /* 分配好的设备号 */
static struct cdev dev;		            /* 初始化以后的字符设备 */

ret = cdev_add(&dev, devid, 1);			/* 将字符设备添加到内核 */
if (ret != 0)
{
    return -1;
}

3、设备卸载函数

卸载驱动时,一定要从内核中删除对应的字符设备。使用的函数是 cdev_del,函数原型如下:

void cdev_del(struct cdev *p);

参数 p 就是要删除的字符设备指针。

三、字符设备加载测试

1、手动创建节点

之后会采取自动创建节点的方式,这里为了测试方便,临时采用手动创建节点的方式,需要将驱动模块代码编译加载到内核以后,使用 cat /proc/devices 查看主设备号。(驱动模块代码在最后

insmod chrdevbase.ko    # 加载驱动模块
cat /proc/devices       # 查看主设备号

设备名为 chrdevbase,主设备号为 248,次设备号为0(因为只注册了一个设备,次设备号默认为0)。手动创建节点使用 mknod 命令

# 命令格式:mknod  /dev/设备名  c  主设备号  次设备号
mknod /dev/chrdevbase c 200 0

 

2、应用测试代码

将应用程序代码交叉编译以后得到的执行文件是 chrdevbaseApp,此时在开发板的命令行中输入 

./chrdevbaseApp /dev/chrdevbase

应用程序测试代码如下: 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char* argv[])
{
    char* driver_path = argv[1];       // 位置0 保存的是 ./chrdevbaseApp

    int fd = open(driver_path, O_WRONLY);    // 对应 open 操作
    if (fd < 0)
    {
        perror("open file failed");
        return -2;
    }

    write(fd, (char*)'a', 1);            // 对应 write 操作

    close(fd);                           // 对应 close 操作
    return 0;
}

3、驱动模板代码

#include <linux/module.h>		// MODULE_LICENSE、MODULE_AUTHOR
#include <linux/init.h>			// module_init、module_exit
#include <linux/printk.h>		// printk
#include <linux/kdev_t.h>		// MKDEV
#include <linux/fs.h>			// register_chrdev_region
#include <linux/cdev.h>			// struct cdev

#define CHRDEVBASE_NAME "chrdevbase" 	/* 设备名 */

static struct chrdev_t{
	dev_t devid;			/* 设备号(由主设备号和次设备号组成) */
	int major;				/* 主设备号 */
	int minor;				/* 次设备号 */

	struct cdev dev;		/* 字符设备 */
} chrdev;

/*
 * @description 	: 打开设备
 * @param – pinode 	: 传递给驱动的 inode
 * @param - pfile 	: 设备文件,file 结构体有个叫做 private_data 的成员变量
 * 一般在 open 的时候将 private_data 指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *pinode, struct file *pfile)
{
    /* 用户实现具体功能 */
	printk("open chrdevbase\n");
    return 0;
}

/*
 * @description 	: 从设备读取数据
 * @param - pfile	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 缓冲区长度
 * @param - offset	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t chrdevbase_read(struct file *pfile, char __user *buf, size_t cnt, loff_t *offset)
{
    printk("read chrdevbase\n");
    return 0;
}

/*
 * @description 	: 向设备写数据
 * @param - pfile	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 要给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offset	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t chrdevbase_write(struct file *pfile, const char __user *buf, size_t cnt, loff_t *offset)
{
	printk("write chrdevbase\n");
    return cnt;
}

/*
 * @description 	: 关闭/释放设备
 * @param - pfile	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_release (struct inode *pinode, struct file * pfile)
{
    /* 用户实现具体功能 */
	printk("close chrdevbase\n");
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevfops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,    // 将chrdevbase_open的函数地址传递给 .open 成员
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int ret = 0;
	printk("chrdevbase init!\n");
	/* 1、注册设备号 */
	if (!chrdev.major)
	{
		ret = alloc_chrdev_region(&chrdev.devid, 0, 1, CHRDEVBASE_NAME);
		chrdev.major = MAJOR(chrdev.devid);
		chrdev.minor = MINOR(chrdev.devid);
	}
	else
	{
		chrdev.devid = MKDEV(chrdev.major, 0);
		ret = register_chrdev_region(chrdev.devid, 1, CHRDEVBASE_NAME);
	}

	/* 2、初始化字符设备 */
	chrdev.dev.owner = THIS_MODULE;
	cdev_init(&chrdev.dev, &chrdevfops);

	/* 3、加载字符设备 */
	ret = cdev_add(&chrdev.dev, chrdev.devid, 1);
	if (ret != 0)
	{
		return -1;
	}

	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	printk("chrdevbase exit!\n");
	/* 释放设备号 */
	unregister_chrdev_region(chrdev.devid, 1);
	/* 卸载设备号 */
	cdev_del(&chrdev.dev);
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);		// 注册 ko模块被加载到内核,系统会调用的函数
module_exit(chrdevbase_exit);		// 注册 ko模块从内核卸载,系统会调用的函数

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");
### 关于STM32 BL24驱动开发指南 对于STM32系列微控制器而言,BL24通常指的是基于SPI接口的LCD显示屏。为了实现对这类显示器的支持,在嵌入式Linux环境下,尤其是针对STM32MP1平台,可以采用DRM框架来完成图形化界面的应用。 然而,具体到STM32 BL24屏幕的驱动开发,主要工作集中在配置合适的硬件资源以及编写相应的初始化代码上。由于提供的参考资料并未直接涉及BL24的具体细节,以下是综合现有信息给出的一般指导原则: #### 设备树配置 在设备树文件中定义必要的连接属性是非常重要的一步。假设使用的是类似于`stm32mp157d-ev1.dts`这样的基础模板[^2],则需要添加或修改节点以匹配实际使用的外设特性。例如,如果BL24通过SPI通信,则应在`.dts`文件里声明SPI总线及其子节点,并设置合理的参数如频率、模式等。 ```diff &spi1 { pinctrl-names = "default"; pinctrl-0 = <&spi1_pins_a>; status = "okay"; + bl24: lcd@0 { + compatible = "bl24,display"; /* 自定义兼容字符串 */ + reg = <0>; /* SPI从机地址 */ + spi-max-frequency = <10000000>; /* 设置最大传输速率 */ + + display-timings { /* 定义显示时序 */ + native-mode = <&timing0>; + timing0: timing0 { + clock-frequency = <9216000>; + hactive = <240>; + vactive = <320>; + hfront-porch = <10>; + hback-porch = <2>; + vsync-len = <2>; + hsync-len = <2>; + vback-porch = <2>; + de-active = <1>; + pixelclk-active = <0>; + hsync-active = <0>; + vsync-active = <0>; + }; + }; + port { + bl24_out: endpoint { + remote-endpoint = <&lcdc_in>; + }; + }; + }; }; ``` 此部分描述了如何向现有的设备树源码中加入新的组件——即BL24液晶屏的信息。这不仅限定了物理层面上的数据交换方式(这里选用SPI),还规定了一些基本的工作条件,像刷新率和分辨率等。 #### 初始化与控制逻辑 一旦完成了上述静态设定之后,就需要考虑动态方面的事情了。这部分涉及到具体的编程实践,包括但不限于加载固件映像、发送指令序列给目标器件等等。考虑到不同厂家生产的BL24可能有所差异,因此建议参考官方文档获取最精确的操作流程。 下面是一段简单的Python伪代码片段用来说明这一过程的大致思路: ```python import spidev from time import sleep class Bl24Display: def __init__(self): self.spi = spidev.SpiDev() self.spi.open(0, 0) # 打开SPI通道 self.spi.max_speed_hz = 10_000_000 def send_command(self, cmd): """ 发送命令 """ self.spi.writebytes([cmd]) def init_display(self): """ 屏幕初始化 """ commands = [ (0x11), # Sleep Out sleep(0.1), (0xB1, ...), # Frame Rate Control ... (0x29) # Display On ] for item in commands: if isinstance(item, tuple): self.send_command(*item) elif callable(item): # 对sleep函数特殊处理 item() if __name__ == '__main__': disp = Bl24Display() disp.init_display() ``` 这段脚本展示了怎样利用Python库spidev来进行低级别的I/O操作,从而达到控制外部设备的目的。当然,在真实的项目当中可能会更加复杂一些,特别是当涉及到多任务调度或是与其他系统的交互时。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值