一、专栏介绍
本专栏内容是记录博主在学习深圳百问网科技有限公司韦东山老师开设的《驱动入门实验班》课程过程中,博主编写、调试、运行在开发板上的课程中涉及的所有驱动试验项目,连载。
课程链接(免费):【Linux驱动入门实验班-B站】 https://www.bilibili.com/video/BV1XK411D7wK/?p=14&share_source=copy_web&vd_source=1bef10f645d499b8efc7d90d0814f719
官方代码仓库(开源):https://e.coding.net/weidongshan/linux_course/linux_basic_develop.git
硬件:100ASK_IMX6ULL_PRO开发板,芯片:NXP IMX6ULL,架构:Cortex-A7 单核。有兴趣可搜索百问网官方网站获取详情。
版权:本篇中代码版权属于韦东山老师。
二、 项目介绍
简单字符设备char_device驱动程序:
-
构造file_operations结构体
-
在里面填充open/read/write/release等成员
-
-
注册file_operations结构体
-
int major = register_chrdev(0, "name", &fops);
-
-
入口函数module_init:调用register_chrdev(注册字符设备cat /proc/devices可查看)、class_create(先创建类)、device_create(后创建设备文件ls /dev/***可查看)
-
出口函数module_exit:调用unregister_chrdev(注销字符设备)、device_destroy(先销毁设备文件)、class_destroy(后销毁类)
三、代码
3.1 驱动代码(内核态)
/*
*Copyright (c)2024, CSDN
*All Rights Reserved.
*文件名称: hello_drv.c
*作者:孙家明
*完成日期: 2024.3.24日
*版本号:V1.0
*项目描述:创建一个简单字符设备文件,内核态下实现对该设备的典型访问操作:open/close/read/write,用户态下编写测试代码实现前述操作。
*项目输出: open/close/read/write每个函数被调用时内核态都会有printk打印,用户态读写文件失败报错,读文件成功返回读取结果(字符串)。
*/
#include "asm/uaccess.h"
#include "linux/err.h"
#include "linux/kdev_t.h"
#include "linux/printk.h"
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/device.h>
#include <linux/io.h>
#include <linux/uio.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#define BUF_LEN 100
/*macro printk*/
#define DRV_PRINTK(words...) \
do{ \
printk("FUNC:%s LINE:%d, ", __FUNCTION__, __LINE__);\
printk(words); \
}while(0);
/*main device id*/
static unsigned int g_hello_major = 0;
/*data buffer, copy from/to user*/
static unsigned char data_buf[BUF_LEN] = {0};
/*hello class*/
static struct class *g_hello_class = NULL;
static ssize_t hello_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
unsigned int len = size>100 ? 100 : size;
printk("%s %s line=%d\n", __FILE__, __FUNCTION__, __LINE__);
copy_to_user(buf, data_buf, len);
return len;
}
static ssize_t hello_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
unsigned int len = size>100 ? 100 : size;
printk("%s %s line=%d\n", __FILE__, __FUNCTION__, __LINE__);
copy_from_user(data_buf, buf, len);
return len;
}
static int hello_open (struct inode *node, struct file *filp)
{
printk("%s %s line=%d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
static int hello_release (struct inode *node, struct file *flip)
{
printk("%s %s line=%d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/* 1. create file operations */
static const struct file_operations hello_drv = {
.owner = THIS_MODULE,
.read = hello_read,
.write = hello_write,
.open = hello_open,
.release = hello_release
};
/* 2. register chrdey */
/* 3. entry function */
static int __init hello_init(void)
{
printk("%s line=%d\n", __FILE__, __LINE__);
g_hello_major = register_chrdev(0, "100ask_hello", &hello_drv);
/*first class, then device*/
g_hello_class = class_create(THIS_MODULE, "hello_class");
if (IS_ERR_OR_NULL(g_hello_class))
{
DRV_PRINTK("Failed to create class, err!\n");
return PTR_ERR(g_hello_class);
}
device_create(g_hello_class, NULL, MKDEV(g_hello_major, 0),
NULL, "hellodev");
return 0;
}
/* 4. exit function */
static void __exit hello_exit(void)
{
printk("%s line=%d\n", __FILE__, __LINE__);
unregister_chrdev(g_hello_major, "100ask_hello");
/*first device, then class*/
device_destroy(g_hello_class, MKDEV(g_hello_major, 0));
class_destroy(g_hello_class);
return;
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL v2");
3.2 驱动测试代码(用户态)
/*
*Copyright (c)2024, CSDN
*All Rights Reserved.
*文件名称: hello_drv_test.c
*作者:孙家明
*完成日期: 2024.3.24日
*版本号:V1.0
*项目描述:创建一个简单字符设备文件,内核态下实现对该设备的典型访问操作:open/close/read/write,用户态下编写测试代码实现前述操作。
*项目输出: open/close/read/write每个函数被调用时内核态都会有printk打印,用户态读写文件失败报错,读文件成功返回读取结果(字符串)。
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
/*
写 ./hello_test /dev/xxx hello_world
读 ./hello_test /dev/xxx
*/
#define BUF_LEN 100
int main(int argc, char *argv[])
{
int fd = -1;
int ret = 0;
char buf[BUF_LEN] = {0};
if(argc < 2)
{
printf("Usage: ");
printf("%s <dev> [string]\n", argv[0]);
return -1;
}
/*open*/
fd = open(argv[1], O_RDWR);
if(0 > fd)
{
printf("cannot open file: %s, err!\n", argv[1]);
return -1;
}
if(argc == 3)
{
/*write*/
ret = write(fd, argv[2], strlen(argv[2])+1);
if(0 > ret)
{
printf("cannot write file: %s, err!\n", argv[1]);
return -1;
}
}
else
{
/*read*/
ret = read(fd, buf, BUF_LEN);
if(0 > ret)
{
printf("cannot read file: %s, err!\n", argv[1]);
return -1;
}
buf[BUF_LEN-1] = '\0';
printf("read from %s: %s\n", argv[1], buf);
}
/*close*/
close(fd);
return 0;
}
四、试验步骤及结果
/*加载模块*/
[root@100ask:/mnt/test_proj/02_hello_drv_transfer_data]# insmod hello_drv.ko
[ 3281.343016] hello_init line=69
/*显示模块*/
[root@100ask:/mnt/dri_labclass/01_hello_drv]# lsmod
Module Size Used by
hello_drv 2919 0
/*写操作*/
[root@100ask:/mnt/test_proj/02_hello_drv_transfer_data]# ./hello_drv_test /dev/hellodev sjmsjmsjm
[ 3355.603301] /home/book/nfs_rootfs/dri_labclass/02_hello_drv_transfer_data/hello_drv.c hello_open line=47
[ 3355.613594] /home/book/nfs_rootfs/dri_labclass/02_hello_drv_transfer_data/hello_drv.c hello_write line=40
[ 3355.625335] /home/book/nfs_rootfs/dri_labclass/02_hello_drv_transfer_data/hello_drv.c hello_release line=52
/*读操作*/
[root@100ask:/mnt/test_proj/02_hello_drv_transfer_data]# ./hello_drv_test /dev/hellodev
[ 3364.622620] /home/book/nfs_rootfs/dri_labclass/02_hello_drv_transfer_data/hello_drv.c hello_open line=47
[ 3364.632571] /home/book/nfs_rootfs/dri_labclass/02_hello_drv_transfer_data/hello_drv.c hello_read line=31
read from /dev/hellodev: sjmsjmsj[ 3364.644812] /home/book/nfs_rootfs/dri_labclass/02_hello_drv_transfer_data/hello_drv.c hello_release line=52
/*卸载模块*/
[root@100ask:/mnt/test_proj/02_hello_drv_transfer_data]# rmmod hello_drv.ko
[ 3378.773476] hello_exit line=88