目录
编写步骤:
设备号的申请注册注销:
通用函数(静态动态申请都可以):
静态申请:
动态申请:
设备号的注销:
设备节点的创建与销毁:
手动创建设备节点:
自动创建设备节点:
设备节点的销毁:
IO资源的映射与配置:
读写函数编写,操作IO口,比如点灯:
应用实验程序的编写
驱动示例代码:
编写步骤:
/*编写步骤:
*
*1、编写驱动模块的基本框架 https://blog.csdn.net/shenlong1356/article/details/88367429
*2、注册注销设备号
*3、创建设备节点
*4、编写file_operations 结构体成员 open write .....
*5、点灯LED1 GPF4 查出寄存器地址 进行物理地址到虚拟地址的映射 ioremap iounmap
*/
设备号的申请注册注销:
通用函数(静态动态申请都可以):
register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
major :确定一个主设备号,如果major=0,则会自动分配设备号
name : 设备名
fops : 构造一个file_operations结构体, 然后放在chrdevs数组中
#define ledmajor 250
const struct file_operations led_fops = {
.open = led_open ,
.read = led_read ,
.write = led_write ,
.release = led_close ,
};
int ret;
ret = register_chrdev(ledmajor , "led_test" , &led_fops) ; //注册设备号
静态申请:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
/* 参数: dev_t from - 要申请的设备号(起始) unsigned count - 要申请的设备号数量 const char *name - 设备名 返回值: 成功:0 失败:负数(绝对值是错误码)*/
动态申请:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
/* 参数: dev_t *dev - 用于保存分配到的第一个设备号(起始) unsigned baseminor - 起始次设备号 unsigned count - 要分配设备号的数量 const char *name - 设备名 返回值: 成功:0 失败:负数(绝对值是错误码)*/
设备号的注销:
unregister_chrdev(unsigned int major, const char * name);
void unregister_chrdev_region(dev_t from, unsigned count); /* 参数: dev_t from - 要释放的第一个设备号(起始) unsigned count - 要释放的次设备号数量 */
设备节点的创建与销毁:
手动创建设备节点:
- 使用mknod手工创建:mknod filename type major minor
自动创建设备节点:
利用udev(mdev)来实现设备文件的自动创建
struct class *class_create(struct module *owner, const char *name); /* 功能:在/sys/class目录下创建一个目录,目录名是name指定的 参数: struct module *owner - THIS_MODULE const char *name - 设备名 返回值: 成功:class指针 失败: - bool IS_ERR(const void *ptr) 判断是否出错 long PTR_ERR(const void *ptr) 转换错误码 */
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...); /* 功能: 在class指针指向的目录下再创建一个目录,目录名由const char *fmt, ...指出、并导出设备信息(dev_t) 参数: struct class *cls - class指针 struct device *parent - 父对象,NULL dev_t devt - 设备号 void *drvdata - 驱动私有数据 const char *fmt, ... - fmt是目录名字符串格式,...就是不定参数 返回值: 成功 - device指针 失败 - bool IS_ERR(const void *ptr) 判断是否出错 long PTR_ERR(const void *ptr) 转换错误码 */
static struct class *ledclass ;
static struct device *led_dev;
ledclass = class_create( THIS_MODULE , "led_class");
device_create(ledclass , NULL , MKDEV(250 , 0), "led_device") ; //创建设备节点
设备节点的销毁:
void class_destroy(struct class *cls); /* 功能:删除class指针指向的目录 参数: struct class *cls - class指针 */
void device_destroy(struct class *cls, dev_t devt); /* 功能:删除device_create创建的目录 参数: struct class *cls - class指针 dev_t devt - 设备号 */
device_destroy(ledclass, MKDEV(250 , 0)); //注意二者的顺序
class_destroy(ledclass);
IO资源的映射与配置:
#define GPFCON 0x56000050 //控制寄存器物理地址
#define GPFDAT 0x56000054 //数据寄存器
#define GPF_SIZE 8
volatile unsigned long *gpfconvir; //虚拟地址
volatile unsigned long *gpfdatvir;
gpfconvir = (volatile unsigned long *)ioremap(GPFCON, 16); //物理地址到虚拟地址映射
gpfdatvir = gpfconvir + 1;
*gpfconvir &= ~(3<<8); //对8 9 bit 清零 也就是 GPF4
*gpfconvir |= (1<<8) ; //配置为输出
读写函数编写,操作IO口,比如点灯:
unsigned long copy_to_user(void *to, const void *from, unsigned long n)
to:目标地址(用户空间)
from:源地址(内核空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数一般在read函数中调用,传递数据给用户(用户读数据)
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
to:目标地址(内核空间)
from:源地址(用户空间)
n:将要拷贝数据的字节数
返回:成功返回0,失败返回没有拷贝成功的数据字节数一般在write函数中调用接受来自用户的数据(用户写数据)
ssize_t led_read (struct file *filp, char __user *buf , size_t count, loff_t *fops)
{
int value = 110 ;
int ret;
ret = copy_to_user(buf, &value, count);
if(ret > 0)
{
printk("read filed\n");
return -EFAULT;
}
return 0 ;
}
/*操作灯函数*/
ssize_t led_write (struct file *filp, const char __user * buf, size_t count, loff_t *fops)
{
int value ;
int ret ;
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("write filed!\n");
return -EFAULT;
}
if(value > 0)
{
*gpfdatvir |= (1<<4); //灭灯
}
else
{
*gpfdatvir &= ~(1<<4); //亮灯
}
return 0;
}
应用实验程序的编写
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
int value = 0;
fd = open("/dev/led_device", O_RDWR); //打开对应的设备节点led_device
if(fd < 0)
{
perror("open\n");
exit(1);
}
//控制灯的亮灭 实现灯的闪烁
while(1)
{
value = 0;
write(fd, &value, 4);
sleep(1);
value = 1;
write(fd, &value, 4);
sleep(1);
}
close(fd);
return 0 ;
}
驱动示例代码:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h> //字符设备头文件
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/*编写步骤:
*
*1、编写驱动模块的基本框架
*2、注册注销设备号
*3、创建设备节点
*4、编写file_operations 结构体成员 open write .....
*5、点灯LED1 GPF4 查出寄存器地址 进行物理地址到虚拟地址的映射 ioremap iounmap
*/
#define ledmajor 250
static struct class *ledclass ;
static struct device *led_dev;
#define GPFCON 0x56000050 //控制寄存器物理地址
#define GPFDAT 0x56000054 //数据寄存器
#define GPF_SIZE 8
volatile unsigned long *gpfconvir; //虚拟地址
volatile unsigned long *gpfdatvir;
ssize_t led_read (struct file *filp, char __user *buf , size_t count, loff_t *fops)
{
int value = 110 ;
int ret;
ret = copy_to_user(buf, &value, count);
if(ret > 0)
{
printk("read filed\n");
return -EFAULT;
}
return 0 ;
}
/*操作灯函数*/
ssize_t led_write (struct file *filp, const char __user * buf, size_t count, loff_t *fops)
{
int value ;
int ret ;
ret = copy_from_user(&value, buf, count);
if(ret > 0)
{
printk("write filed!\n");
return -EFAULT;
}
if(value > 0)
{
*gpfdatvir |= (1<<4); //灭灯
}
else
{
*gpfdatvir &= ~(1<<4); //亮灯
}
return 0;
}
int led_open (struct inode *inod, struct file *filp)
{
// printk("open ok\n");
return 0;
}
int led_close(struct inode *inod, struct file *filp)
{
// printk("close ok\n");
return 0;
}
const struct file_operations led_fops = {
.open = led_open ,
.read = led_read ,
.write = led_write ,
.release = led_close ,
};
static int __init led_drv_init(void)
{
int ret;
ret = register_chrdev(ledmajor , "led_test" , &led_fops) ; //注册设备号
if (ret == 0)
{
printk("register ok!\n" );
}
else
{
printk("register filed!\n");
return -1;
}
gpfconvir = (volatile unsigned long *)ioremap(GPFCON, 16); //物理地址到虚拟地址映射
gpfdatvir = gpfconvir + 1;
*gpfconvir &= ~(3<<8); //对8 9 bit 清零 也就是 GPF4
*gpfconvir |= (1<<8) ; //配置为输出
ledclass = class_create( THIS_MODULE , "led_class");
device_create(ledclass , NULL , MKDEV(250 , 0), "led_device") ; //创建设备节点
return 0;
}
static void __exit led_drv_exit(void)
{
iounmap(gpfconvir); //释放io资源
device_destroy(ledclass, MKDEV(250 , 0)); //注意三者的顺序
class_destroy(ledclass);
unregister_chrdev(ledmajor , "led_test") ; //注销设备号
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
错误总结:
寄存器数据类型要正确,寄存器地址映射要正确;