上一篇文章,记录了模块入口和出口函数的编写。
这一篇要继续编写字符驱动程序了。
仍然参考正点原子:
第3.3讲 我的第一个Linux驱动-字符设备驱动框架搭建实验_哔哩哔哩_bilibili
驱动注册和卸载
字符驱动设备的注册函数位register_chrdev
卸载函数为unregister_chrdev
linux/fs.h :
static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{return __register_chrdev(major, 0, 256, name, fops);
}static inline void unregister_chrdev(unsigned int major, const char *name)
{__unregister_chrdev(major, 0, 256, name);
}
目前的代码:
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME "chrdevbase" //设备名称static int __init chrdevbase_init(void)
{int major;printk(KERN_EMERG "chrdevbase_init\r\n");/*character device: register*/major = register_chrdev(CHRDEVBASE_MAJOR,CHEDEVBASE_NAME,&chrdevbase_fops);return 0;
}/*module exit*/
static void __exit chrdevbase_exit(void)
{printk(KERN_EMERG "chrdevbase_exit\r\n");/*character device: unregister*/unregister_chrdev(CHRDEVBASE_MAJOR,CHEDEVBASE_NAME);
}/* 驱动模块入口和出口函数注册 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);MODULE_AUTHOR("Skylar <skylar@33.com>");
MODULE_DESCRIPTION("FMQL Chrdevbase Driver");
MODULE_LICENSE("GPL");
命令cat /proc/devices 可以查看当前已使用的设备号,主设备号不能重复。
设备号
dev_t为无符号32位(u32),包括主设备号(高12位)和次设备号(低20位)
/linux/kdev_t.h :
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))#define print_dev_t(buffer, dev) \sprintf((buffer), "%u:%u\n", MAJOR(dev), MINOR(dev))
MAJOR与MINOR宏定义可以获取dev的主/次设备号,MKDEV可以把主/次设备号拼接成一个设备。
file_operations结构体
file_operations结构体为设备的操作函数的集合。添加需要的操作即可。
参考gpio.c的写法(其他文件都可):
static const struct file_operations gpio_fops = {.owner = THIS_MODULE,.poll = gpio_poll,.unlocked_ioctl = gpio_ioctl,.write = gpio_write,.open = gpio_open,.release = gpio_release,.llseek = noop_llseek,
};
.owner是必须写的,照着写就行。
open和release函数是成对出现;write和read也是。
因此,添加以下代码:
/* file_oprations*/
static const struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,.write = chrdevbase_write,.open = chrdevbase_open,.release = chrdevbase_release,.read = chrdevbase_read,
};
之后,给出上述四个函数的定义:(继续参考)
#include <linux/fs.h>static int chrdevbase_open(struct inode *inode, struct file *filp)
{printk("chrdevbase_open\r\n");return 0;
}static int chrdevbase_release(struct inode *inode, struct file *filp)
{printk("chrdevbase_release\r\n");return 0;
}static ssize_t chrdevbase_write(struct file *file, const char __user *buf,size_t count, loff_t *off)
{printk("chrdevbase_write\r\n");return 0;
}static ssize_t chrdevbase_read(struct file *file, char __user *buf, size_t count,loff_t *offset)
{printk("chrdevbase_read\r\n");return 0;
}
makefile编译,生成可执行文件。
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : chrdevbase.c
作者 : 正点原子
版本 : V1.0
描述 : chrdevbase驱动文件。
其他 : 无
论坛 : www.openedv.com
日志 : 初版V1.0 2019/1/30 左忠凯创建
修改 :v3.0 2023/6/8 正点原子
***************************************************************/#define CHRDEVBASE_MAJOR 200 // 主设备号
#define CHRDEVBASE_NAME "chrdevbase" // 设备名static char readbuf[100]; // 读缓冲区
static char writebuf[100]; // 写缓冲区
static char kerneldata[] = {"kernel data!"};/** @description : 打开设备* @param – inode : 传递给驱动的inode* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量* 一般在open的时候将private_data指向设备结构体。* @return : 0 成功;其他 失败*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{printk("chrdevbase open!\r\n");return 0;
}/** @description : 从设备读取数据* @param - filp : 要打开的设备文件(文件描述符)* @param - buf : 返回给用户空间的数据缓冲区* @param - cnt : 要读取的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用户空间发送数据 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}//printk("chrdevbase read!\r\n");return 0;
}/** @description : 向设备写数据* @param - filp : 设备文件,表示打开的文件描述符* @param - buf : 要写给设备写入的数据* @param - cnt : 要写入的数据长度* @param - offt : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}//printk("chrdevbase write!\r\n");return 0;
}/** @description : 关闭/释放设备* @param - filp : 要关闭的设备文件(文件描述符)* @return : 0 成功;其他 失败*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{//printk("chrdevbase release!\r\n");return 0;
}/** 设备操作函数结构体*/
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};/** @description : 驱动入口函数* @param : 无* @return : 0 成功;其他 失败*/
static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase_init()\r\n");return 0;
}/** @description : 驱动出口函数* @param : 无* @return : 无*/
static void __exit chrdevbase_exit(void)
{/* 注销字符设备驱动 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase_exit()\r\n");
}/** 将上面两个函数指定为驱动的入口和出口函数*/
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);/** LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("alientek");
测试APP
APP.c为Linux应用开发的程序。
比如,想查看open函数如何调用:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : chrdevbaseApp.c
作者 : 正点原子
版本 : V1.0
描述 : chrdevbase驱测试APP。
其他 : 使用方法:./chrdevbaseApp /dev/chrdevbase <1>|<2>argv[2] 1:读文件argv[2] 2:写文件
论坛 : www.openedv.com
日志 : 初版 V1.0 2019/1/30 正点原子创建
修改 : V3.0 2023/6/8 正点原子
***************************************************************/static char usrdata[] = {"usr data!"};/** @description : main主程序* @param - argc : argv数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd, retvalue;char *filename;char readbuf[100], writebuf[100];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开驱动文件 */fd = open(filename, O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", filename);return -1;}if(atoi(argv[2]) == 1){ /* 从驱动文件读取数据 */retvalue = read(fd, readbuf, 50);if(retvalue < 0){printf("read file %s failed!\r\n", filename);}else{/* 读取成功,打印出读取成功的数据 */printf("read data:%s\r\n",readbuf);}}if(atoi(argv[2]) == 2){/* 向设备驱动写数据 */memcpy(writebuf, usrdata, sizeof(usrdata));retvalue = write(fd, writebuf, 50);if(retvalue < 0){printf("write file %s failed!\r\n", filename);}}/* 关闭设备 */retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;
}
open函数返回的是文件描述符。
write函数:将userdata传给驱动程序;
read函数:将kerneldata传给
编译APP.c :
以下内容也可参考:
韦东山嵌入式入门笔记之——应用开发基础篇(三)_韦东山 c应用开发-CSDN博客
韦东山嵌入式入门笔记之——应用开发基础篇(四)_framebuffer 应用编程视频-CSDN博客
韦东山嵌入式入门笔记之——开发板上的第一个APP和驱动程序_韦东山 程序自动运行-CSDN博客
手动添加设备
- 执行chrdevbase.ko lsmod检查一下
- 输入命令:mknod /dev/chedevbase c 200 0 创建设备节点
c:字符设备
200:主设备号
0:次设备号
检查一下 ls /dev/chrdevbase -l
- 执行APP:./chedevbaseAPP /dev/chrdevbase 1(或2)
FATAL: Module xxx.ko not found in directory /lib/modules/4.1.15-2.1.0+ Modprobe无法装载模块问题_modules no found in directory-CSDN博客
修改modprobe命令:
but APP运行还是不行:
Linux下-bash: Permission denied 或者 sudo: command not found 错误 - VVingerfly - 博客园
嵌入式linux运行程序 -sh ./xxx: not found 解决办法_51CTO博客_嵌入式linux应用程序
(未完待续。。。)