上一篇文章,记录了模块入口和出口函数的编写。
这一篇要继续编写字符驱动程序了。
仍然参考正点原子:
第3.3讲 我的第一个Linux驱动-字符设备驱动框架搭建实验_哔哩哔哩_bilibili
1-驱动注册和卸载
字符驱动设备的注册函数为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");
module_init与module_exit为驱动模块的加载和卸载(入口和出口函数)。(将驱动编译成模块)
命令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编译,生成可执行文件.ko。
#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");
不知道为什么,printk必须前面加上“KERN_EMERG”才能在控制台显示打印信息:
测试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)
修改modprobe命令:
运行APP
but APP运行还是不行:
Linux下-bash: Permission denied 或者 sudo: command not found 错误 - VVingerfly - 博客园
嵌入式linux运行程序 -sh ./xxx: not found 解决办法_51CTO博客_嵌入式linux应用程序
./APP: Command not found找到解决方法了:
chmod修改APP的权限即可。
read和write都没问题。
2-led程序
通过寄存器去控制led的亮灭,所以需要:
- 查看fmql芯片的GPIO寄存器地址(DATA和DIR)
- 将物理地址映射到虚拟地址指针(因为linux是访问虚拟地址的)
- 通过readl和writel函数访问虚拟地址指针(不建议自己直接访问地址)(l代表的是32位)
- register_chrdev占用了一个主设备号下的所有次设备号,太多了,因此,让linux自行分配设备号,需要用到函数:register_chrdev_region 或alloc_chrdev_region(在__init led_init)
- 代替mknod:自动创建设备节点class_create(在__init led_init)
目前只做了前三个。
led.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/ide.h>
#define LED_MAJOR 200
#define LED_NAME "led_1" /* cat /proc/devices */
#define LEDOFF 0 /* led off */
#define LEDON 1 /* led on */
/* 物理地址 */
#define GPIO_C_DR_BASE (0xE0003200) // data
#define GPIO_C_DDR_BASE (0xE0003204) // direction
#define GPIO_C_INTEN_BASE (0xE0003230) // interrup enable
/* 地址映射后的虚拟指针 */
static void __iomem *FMQL_GPIO_C_DR;
static void __iomem *FMQL_GPIO_C_DDR;
static void __iomem *FMQL_GPIO_C_INTEN;
/* LED - ON / OFF*/
void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON){
val = readl(FMQL_GPIO_C_DR);
val |= (1 << 5);
writel(val, FMQL_GPIO_C_DR);
printk(KERN_EMERG "led on\r\n");
} else if(sta == LEDOFF)
{
val = readl(FMQL_GPIO_C_DR);
val &= ~(1 << 5);
writel(val, FMQL_GPIO_C_DR);
printk(KERN_EMERG "led off\r\n");
}
}
static int led_open(struct inode *inode, struct file *flip)
{
printk(KERN_EMERG "led_open\r\n");
return 0;
}
static int led_release(struct inode *inode, struct file *flip)
{
printk(KERN_EMERG "led_release\r\n");
return 0;
}
static ssize_t led_write(struct file *file, const char __user *buf,
size_t count, loff_t *offset)
{
int retvalue = 0;
unsigned char databuf[1];
printk(KERN_EMERG "led_write\r\n");
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0){
printk(KERN_EMERG "write led failed\r\n");
return -EFAULT;
} else {
printk(KERN_EMERG "write led success\r\n");
}
// if(databuf[0] == LEDON){ // Light on
//
// }
led_switch(databuf[0]);
return 0;
}
/* file_operations */
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.write = led_write,
};
/* module_init */
static int __init led_init(void)
{
int ret = 0;
unsigned int val = 0;
printk(KERN_EMERG "led_init\r\n");
/* 1- 地址映射 */
FMQL_GPIO_C_DR = ioremap(GPIO_C_DR_BASE, 4); // 32bit
FMQL_GPIO_C_DDR = ioremap(GPIO_C_DDR_BASE, 4);
FMQL_GPIO_C_INTEN = ioremap(GPIO_C_INTEN_BASE, 4);
/* 2- 初始化 */ // read, change val, write
val = readl(FMQL_GPIO_C_DDR);
val &= ~(1 << 5); // 0x1 << 5 : clear bit5
val |= 1 << 5; // bit5 set 1 : output
writel(val, FMQL_GPIO_C_DDR);
/* 3- 点灯 */
val = readl(FMQL_GPIO_C_DR);
val |= (1 << 5);
writel(val, FMQL_GPIO_C_DR); // Light LED
ret = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);
if (ret < 0){
//fail
printk(KERN_EMERG "register_chrdev led: fail\r\n");
return -EIO;
} else {
printk(KERN_EMERG "register_chrdev led: succeed\r\n");
}
return 0;
}
/* module_exit */
static void __exit led_exit(void)
{
unsigned int val = 0;
printk(KERN_EMERG "led_exit");
/* 3- 灭灯 */
val = readl(FMQL_GPIO_C_DR);
val &= ~(1 << 5);
writel(val, FMQL_GPIO_C_DR);
/* 1- 取消地址映射*/
iounmap(FMQL_GPIO_C_DR);
iounmap(FMQL_GPIO_C_DDR);
iounmap(FMQL_GPIO_C_INTEN);
unregister_chrdev(LED_MAJOR, LED_NAME);
}
module_init(led_init);
module_exit(led_exit);
/* info */
MODULE_AUTHOR("Skylar");
MODULE_DESCRIPTION("FMQL LED Driver");
MODULE_LICENSE("GPL");
ledAPP.c
/*
* led
* 测试APP
* 使用方法:./ledAPP /dev/led <0>|<1>
* argv[2] 0: LED off
* argv[2] 1: LED on
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "stdlib.h"
#include "string.h"
#include "stdio.h"
#include "unistd.h"
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int ret, fd;
char databuf[0];
char* filename = argv[1];
if(argc != 3){
printf("Error Usage!\r\n");
return -1;
}
/* 1- open */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("APP open %s error\r\n", filename);
return -1;
} else{
printf("APP open %s success\r\n", filename);
}
/* 2- led on/off */
databuf[0] = atoi(argv[2]); /* char --> int */
ret = write(fd,databuf[0], sizeof(databuf));
if(ret < 0){
printf("APP Turn on/off LED failed\r\n");
close(fd);
return -1;
} else{
printf("APP Turn on/off LED success\r\n");
}
/* 1- close */
ret = close(fd);
if(ret < 0){
printf("APP close %s error\r\n", filename);
return -1;
} else{
printf("APP close %s success\r\n", filename);
}
return 0;
}
运行
一次是0,一次是1,但是右边显示的都是led off。
而且ledAPP中的printf没有打印出来。
分析:能控制灭,说明寄存器是对的,GPIO的位也是对的。
不能控制亮:程序写错了(逻辑),或者是参数传递错误?
修改代码
ledAPP.c :
databuf原来定义的是databuf[0],修改成databuf[1]试试。
好使了!
Makefile
为了不需要每次打开终端都配置source,所以修改makefile:
这样就自动配置了。(但是echo $PETALINUX是空白的,所以可能并不好用)
这样写会报错:
手动输入命令就可以:
(未完待续。。。)