四、Linux杂项设备驱动编写


  

简介

在 Linux 下有 3 种设备驱动:
(1)字符设备: 字符设备的特点是在 IO 传输的过程中是没有缓冲的,比如串口,i2c,等。
(2)块设备: 块设备也就是存储设备,例如 ddr 内存条,sd 卡,u 盘,emmc,nandflash 这种,它
们的传输是有缓冲的。
(3)网络设备: 网络设备例如以太网,wifi,蓝牙,2/3/4G 模块。一种器件可以同时是 2 种设备,例如蓝牙,它是网络设备,也是字符设备。

一、Linux杂项驱动意义

  有很多简单的外围字符设备,它们功能相对简单,一个设备占用一个主设备号对于内核资源来说太浪费。所以对于这些简单的字符设备它们共用一个主设备号,不同的设备使用不同的次设备号。

二、杂项驱动特点

(1)主设备号相同,次设备号不同。
(2)在文件系统中自动生成设备节点。

三、过程概述

编写杂项设备驱动节点给用户使用,编写应用层app.c测试驱动功能是否实现。

四、常用API

包含的头文件#include <linux/miscdevice.h>

int misc_register(struct miscdevice * misc);  //注册
int misc_deregister(struct miscdevice *misc);  //卸载

五、重要结构体

(1)miscdevice 结构体。 包含的头文件:#include <linux/miscdevice.h>

struct miscdevice {
int minor; //次设备号
const char *name; //驱动的名字,/dev 下驱动节点的名字也是这个
const struct file_operations *fops; //文件操作集,重点
struct list_head list; //内核链表
struct device *parent; //父设备
struct device *this_device; //额外的参数
const char *nodename;
umode_t mode;
};

(2)file_operations 结构。 包含的头文件:#include <linux/fs.h>
  这个结构体是文件操作集结构体,记录了应用层的 IO 和内核函数的映射关系。杂项设备中应用层和内核层传输数据就是通过文件操作集来实现的。

struct file_operations {
struct module *owner; //拥有者
//当应用层 read 驱动节点时,会触发这个指针指向的函数
ssize_t (*read) (struct file *, char __user *, size_t, loff_t*);
//当应用层 write 驱动节点时,就会触发这个指针指向的函数
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//当应用层 ioctl 驱动节点时,就会触发这个指针指向的函数
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//当应用层 open 驱动节点时,就会触发这个指针指向的函数
int (*open) (struct inode *, struct file *);
//当应用层 close 驱动节点时,就会触发这个指针指向的函数
int (*release) (struct inode *, struct file *);
};

六、应用层内核层交互函数。

包含的头文件:#include <linux/uaccess.h>
(1)读取用户层传来的数据

copy_from_user(void * to, const void __user * from, unsigned long n);
//to: 拷贝到哪里
//from: 数据来源
//n: 拷贝的字节数

(2)拷贝内核的数据给用户

copy_to_user(void __user *to, const void *from, unsigned long n);
//to: 拷贝到哪里
//from: 数据来源
//n: 拷贝的字节数

七、杂项设备注册流程

(1) 定义文件操作集。

struct file_operations misc_fops = {
.owner = THIS_MODULE, //拥有者是自己 //其它的先不管,先写这么多
};

(2) 定义一个杂项设备。

struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR, //次设备号可以写死,也可以动态分配,MISC_DYNAMIC_MINOR 是动态分配的意思
.name = "qjl", //在/dev 下生成的节点名字
.fops = &misc_fops, //绑定文件操作集,规定触发关系
};

(3) 把杂项设备注册到内核。

 int ret;
 ret = misc_register(&misc);
 if(ret < 0){
   printk("misc register error\n");
   return ret;
}

八、驱动编写

(1)杂项设备驱动misc.c编写

#include <linux/module.h>     //模块
#include <linux/kernel.h>     //内核
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

#define KBUF_SIZE (1024)
#define MIN(x,y) ( (x)<(y)? (x):(y) )      //判断哪个小,选哪个

char kbuf[KBUF_SIZE];


int misc_open(struct inode * node, struct file * fp)
{
    return 0;
}

int misc_release(struct inode *node, struct file *fp)
{
	return 0;
}

ssize_t misc_read(struct file *fp, char __user *ubuf , size_t size, loff_t *offset)
{
	int ret;
	ret=copy_to_user(ubuf, kbuf, MIN(KBUF_SIZE,size));
	if(ret !=0){
		 pr_err("copy_to_user error\r\n");
		 return -EINVAL;
	}   
	return MIN(KBUF_SIZE,size);   //返回成功读取的字节数
}

ssize_t misc_write(struct file *fp, const char __user *ubuf, size_t size, loff_t *offset)
{
   int ret;
   ret=copy_from_user(kbuf, ubuf, MIN(KBUF_SIZE,size));
   if(ret !=0){
     pr_err("copy_from_user error\r\n");
	 return -EINVAL;
   }
    pr_err("%s\r\n",kbuf);         //打印出用户写来的数据
	return MIN(KBUF_SIZE,size);    //返回成功写入的字节数
}

//描述一个文件操作集
const struct file_operations misc_fops={
  .owner=THIS_MODULE,     //文件操作集的拥有者是 本模块。
  .open =misc_open,       //应用层对本模块的驱动节点open时触发的函数
  .release=misc_release,  //应用层对本模块的驱动节点close时触发的函数
  .read=misc_read,        //应用层对本模块的驱动节点read时触发的函数
  .write=misc_write,      //应用层对本模块的驱动节点write时触发的函数
  
};

//描述一个杂项设备
struct miscdevice misc={
 .minor=MISC_DYNAMIC_MINOR, //由内核自动分配次设备号,misc的主设备号为10.
 .name ="qjl",             //在/dev下生成的驱动节点的名称,不能有空格!
 .fops =&misc_fops,
};

static int __init misc_init(void)
{
   int ret;
   ret=misc_register(&misc);  //向内核注册一个杂项设备
   	if(ret <0){
      pr_err("misc_register error\r\n");
	  return -1;
   }

   return 0;
}

static void __exit misc_exit(void)
{
   misc_deregister(&misc);  //从内核注销一个杂项设备
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");

(2)应用层 app.c编写

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

int main(int argc, char **argv)
{
   int fd;
   size_t w_size;
   size_t r_size;
   char w_buff[128]="hello world!";   //向内核写进的数据
   char r_buff[128];
   
   fd = open("/dev/qjl",O_RDWR);
   if(fd <0){
     perror("open error");
	 return -1;
   }
   w_size=write(fd, w_buff, strlen(w_buff));
   printf("w_size:%ld\n",w_size);
  
   r_size= read(fd, r_buff, sizeof(r_buff));
   printf("w_size:%ld  data:%s\r\n", w_size, r_buff);
   
   close(fd);
   return 0;
}

(2) Makefile编写

#多个c文件生成一个ko文件。
obj-m +=misc.o           #把.c文件的名字加.o写到这里

# KDIR 内核源码路径,根据自己需要设置
KDIR:=/home/qjl/work/lichee/linux-3.10

all:
#ARCH: 指当前编译的驱动模块的架构
#CROSS_COMPILE:指明交叉编译器的前缀
#-C: 指定去$(KDIR)目录下执行Makefile
#M:告知Makefile,需要的编译文件在哪
#modules: 这个规则是用于编译驱动模块的
	@make ARCH=arm64 CROSS_COMPILE=aarch64-linux- -C $(KDIR) M=$(PWD) modules 
	@rm -fr .tmp_versions *.o *.mod.o *.mod.c *.bak *.symvers *.markers *.unsigned *.order *~ .*.*.cmd .*.*.*.cmd
	
clean:
	@make ARCH=arm64 CROSS_COMPILE=aarch64-linux- -C $(KDIR) M=$(PWD) modules clean
	@rm -rf *.ko

九、实现

(1)将编译好的app 和misc.ko文件上传到开发板系统。
(2)安装杂项设备。insmod misc.ko
(3)卸载杂项设备。rmmod misc.ko
(3)通过cat /proc/misc查看杂项设备驱动。
在这里插入图片描述

或使用 ls /dev 查看杂项设备驱动。
在这里插入图片描述
(4)执行app程序。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值