设备驱动基础学习--字符驱动实现
1.标识设备文件
当我们ls -l /dev/sd{a,b} /dev/ttyS{0,1}时,
可以看到,设备文件和普通文件区别不大,主要差异在于:
1)设备类型为b、c,分别表示块设备和字符设备。
2)有主设备号和从设备号。
由于系统可能包含几个同样类型的设备,由同意个设备驱动程序管理。主编号标识设备相连的驱动程序,次编号被内核用来决定引用哪个设备。
设备编号用dev_t表示(linux/types.h 32位,其中12位表示主设备号,20位表示次设备号)。
由dev_t获得主设备号或次设备号:MAJOR(dev_t dev); MINOR(dev_t dev)
已知主设备号和次设备号来获取dev_t类型:MKDEV(int major, int minor)
2.分配一个设备号范围
在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用. 为此目的的必要的函数是 register_chrdev_region, 在 <linux/fs.h>中声明:
int register_chrdev_region(dev_t first, unsigned int count, char *name);
这里, first 是你要分配的起始设备编号. first 的次编号部分常常是 0, 但是没有要求是那个效果. count 是你请求的连续设备编号的总数. 注意, 如果 count 太大, 你要求的范围可能溢出到下一个次编号; 但是只要你要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围的设备的名子; 它会出现在 /proc/devices 和sysfs 中.
也可以动态分配设备编号:int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 调用成功后dev会保存已分配的第一个编号。
释放设备编号:void unregister_chrdev_region(dev_t first, unsigned int count);
3.注册字符设备
内核使用struct cdev结构表示字符设备,所以在内核调用该设备操作之前,需要分配并注册一个或者多个该结构。
1)定义设备的结构:
struct my_dev {
struct cdev cdev; //此处如果定义的是指针类型,则需要申请分配内存
}my_dev;
//my_dev ->cdev = cdev_alloc(); //如果cdev是指针则需要这一步
my_dev->cdev.ops = &my_fops;//设备文件操作函数
my_dev->cdev.owner = THIS_MODULE;
2)再调用cdev_init(struct cdev *cdev, struct file_operations *fops);
3)调用cdev_add(struct cdev *dev, dev_t num, unsigned int count);//count表示设备提供的从设备号的数量。
为从系统去除一个字符设备, 调用: void cdev_del(struct cdev *dev);
4. 设备控制--ioctl
大部分设备驱动程序应该提供给应用程序控制设备的功能,即应用程序可以通过ioctl系统调用来控制设备。
1)接口
用户层的ioctl接口如下,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。
int ioctl(int fd,unsigned long cmd,...);
/*
fd:文件描述符
cmd:控制命令
...:可选参数:插入*argp,具体内容依赖于cmd
*/
设备驱动所对应的ioctl 如下:
int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
/*
inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选。
*/
在驱动程序中实现的ioctl函数体内,实际上是有一个switch {case}结构,每一个case对应一个命令码,做出一些相应的操作。
2)命令码
在Linux核心中是这样定义一个命令码的:
____________________________________
| 设备类型 | 序列号 | 方向 | 数据尺寸 |
|----------|--------|------|-------- |
| 8 bit | 8 bit |2 bit|8~14 bit|
|----------|--------|------|-------- |
这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送
4.应用程序与内核数据交换
copy_to_user -- Copy a block of data into user space.
copy_from_user -- Copy a block of data from user space.
get_user -- Get a simple variable from user space.
put_user -- Write a simple value into user space.
原型如下:
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n);//to是kernel的目标地址,from是用户空间源地址,n是copy的bytes
unsigned long copy_to_user (void __user * to, const void * from, unsigned long n);//to是用户空间目标地址,from是kernel的源地址,n是copy的bytes
put_user ( x, ptr);//x是copy到用户空间的值,ptr是用户空间的目标地址
get_user ( x, ptr);//x是copy到kernel的值,ptr是用户空间的源地址
5.编译驱动
makfile 如下:
ifneq ($(KERNELRELEASE),)
obj-m := fellowcdev.o
module-objs := fellowcdev.o
else
KDIR:=/lib/modules/$(shell uname -r)/build
PWD:=$(shell pwd)
default:
make -C $(KDIR) M=$(PWD) modules
endif
obj-m表示编译出来的模块名为fellowcdev.ko
module-objs表示编译时的源代码为fellowcdev.c.如果有多个源文件,则module-obj := file1.o file2.o
make -C $(KDIR) M=$(PWD) modules,这个命令开始是改变它的目录到用 -C 选项提供的目录下( 就是说, 你的内核源码目录 ). 它在那里会发现内核的顶层 makefile. 这个 M=选项使 makefile在试图建立模块目标前, 回到你的模块源码目录.
这个 makefile 在一次典型的编译中要被读 2 次. 当从命令行中调用这个 makefile , KERNELRELEASE 变量没有设置.执行else后的过程,到存放内核的目录执行其makefile,在执行内核目录makefile过程中会定义KERNELRELEASE,然后M=$(PWD)表示返回到当前目录,再次执行makefile,modules表示编译成模块的意思。而此时KERNELRELEASE已定义在第 2 次读, makefile 设置 obj-m, 并且内核的 makefile 文件完成实际的编译模块工作.
6.实现
fellow_cdev.h
#ifndef _FELLOW_CDEV_H_
#define _FELLOW_CDEV_H_
#include <linux/ioctl.h>
//主设备号
#define FELLOW_CDEV_MAJOR 199
#define FELLOW_CDEV_NR 2
//命令码
#define FELLOW_CDEV_IOC_MAGIC 'f'
#define FELLOW_CDEV_IOC_PRINT _IO(FELLOW_CDEV_IOC_MAGIC, 1)
#define FELLOW_CDEV_IOC_GET _IOR(FELLOW_CDEV_IOC_MAGIC, 2, int)
#define FELLOW_CDEV_IOC_SET _IOW(FELLOW_CDEV_IOC_MAGIC, 3, int)
#define FELLOW_CDEV_IOC_MAXNR 3
#endif
fellowcdev.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "fellow_cdev.h"
struct fellowcdev_dev{//设备文件结构体
struct cdev cdev;
int val;
char *data;
unsigned int size;
};
struct fellowcdev_dev *fellowcdev_devp;
int fellowcdev_open(struct inode *inode, struct file *filep)
{
//struct fellowcdev_dev *dev;
int minor = MINOR(inode->i_rdev);
if (minor >= FELLOW_CDEV_NR)
return -ENODEV;
// dev = container_of(inode->i_cdev, struct fellowcdev_dev, cdev);
// dev = &fellowcdev_devp[minor];
// filep->private_data = dev;
filep->private_data = fellowcdev_devp;
return 0;
}
int fellowcdev_release(struct inode *inode, struct file *filep)
{
return 0;
}
ssize_t fellowcdev_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos)
{
int ret = 0;
printk("fellowcdev_read\n");
loff_t pos = *f_pos;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
if (pos > devp->size)
{
ret = -EINVAL;
goto fail;
}
if (count > devp->size - pos)
{
count = devp->size - pos;
}
pos += count;
if (copy_to_user(buf, devp->data + *f_pos , count))
{
ret = -EFAULT;
goto fail;
}
*f_pos = pos;
return count;
fail:
return ret;
}
ssize_t fellowcdev_write(struct file *filep,const char __user *buf, size_t count, loff_t *f_pos)
{
printk("fellowcdv_write:%s, pos:%d\n", buf,(int) *f_pos);
int ret = 0;
loff_t pos = *f_pos;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
if (pos > devp->size)
{
ret = -EINVAL;
goto fail;
}
if (count > devp->size - pos)
{
count = devp->size - pos;
}
pos += count;
if (copy_from_user( devp->data + *f_pos, buf , count))
{
ret = -EFAULT;
goto fail;
}
printk("write data:%s\n", devp->data);
*f_pos = pos;
return count;
fail:
return ret;
}
loff_t fellowcdev_llseek(struct file *filep, loff_t off, int whence)
{
loff_t pos = filep->f_pos;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
switch(whence)
{
case 0:
pos = off;
break;
case 1:
pos += off;
break;
case 2:
default:
return -EINVAL;
break;
}
if (pos > devp->size || pos < 0)
return -EINVAL;
filep->f_pos = pos;
return filep->f_pos;
}
long fellowcdev_ioctl(struct file *filep,unsigned int cmd,unsigned long arg)
{
int ret = 0;
struct fellowcdev_dev *devp = (struct fellowcdev_dev *)(filep->private_data);
if (_IOC_TYPE(cmd) != FELLOW_CDEV_IOC_MAGIC)
return -EINVAL;
if (_IOC_NR(cmd) > FELLOW_CDEV_IOC_MAXNR)
return -EINVAL;
switch(cmd)
{
case FELLOW_CDEV_IOC_PRINT:
printk("FELLOW_CDEV_IOC_PRINT\n");
printk("val:%d, size: %d, data: %s\n", devp->val, devp->size, devp->data);
break;
case FELLOW_CDEV_IOC_SET:
printk("FELLOW_CDEV_IOC_SET\n");
ret = get_user(devp->val, (int *)arg);
printk("set val:%d\n", devp->val);
break;
case FELLOW_CDEV_IOC_GET:
printk("FELLOW_CDEV_IOC_GET\n");
ret = put_user(devp->val, (int *)arg);
break;
default:
return -EINVAL;
}
return ret;
}
static const struct file_operations fellowcdev_fops ={
.owner = THIS_MODULE,
.open = fellowcdev_open,
.release = fellowcdev_release,
.read = fellowcdev_read,
.write = fellowcdev_write,
.llseek = fellowcdev_llseek,
.unlocked_ioctl = fellowcdev_ioctl,
};
static int fellowcdev_init(void)
{
printk("fellowcdev_init\n");
dev_t devno = MKDEV(FELLOW_CDEV_MAJOR, 0);
int ret = 0;
if ((ret = register_chrdev_region(devno, 1, "fellowcdev") < 0))
return ret;
fellowcdev_devp = kmalloc(sizeof(struct fellowcdev_dev), GFP_KERNEL);
if (!fellowcdev_devp)
{
ret = -ENOMEM;
goto fail;
}
fellowcdev_devp->size = 1024;
fellowcdev_devp->data = kmalloc(1024, GFP_KERNEL);
memset(fellowcdev_devp->data, 0, 1024);
cdev_init(&(fellowcdev_devp->cdev), &fellowcdev_fops);
fellowcdev_devp->cdev.owner = THIS_MODULE;
fellowcdev_devp->cdev.ops = &fellowcdev_fops;
cdev_add(&(fellowcdev_devp->cdev), devno, 1);
return 0;
fail:
unregister_chrdev_region(devno, 1);
return ret;
}
static void fellowcdev_exit(void)
{
cdev_del(&(fellowcdev_devp->cdev));
kfree(fellowcdev_devp);
unregister_chrdev_region(MKDEV(FELLOW_CDEV_MAJOR,0),1);
}
MODULE_AUTHOR("fellow");
MODULE_LICENSE("GPL");
module_init(fellowcdev_init);
module_exit(fellowcdev_exit);
app.c
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include "fellow_cdev.h"
int main(void)
{
int fd = open("/dev/fellowcdv", O_RDWR);
if (fd < 0)
{
printf("open fail:%s\n", strerror(errno));
return -1;
}
int ret = 0;
char data[1024] = {'\0'};
if ((ret = write(fd, "fellow_data", sizeof("fellow_data"))) < 0)
{
printf("write fail:%s\n", strerror(errno));
}
if ((ret = lseek(fd, 0, 0)) < 0)
{
printf("lseek fail:%s\n", strerror(errno));
}
if ((ret = read(fd, data, 1024)) < 0)
{
printf("read fail:%s\n", strerror(errno));
}
printf("read data:%s\n", data);
int val = 18;
if ((ret = ioctl(fd, FELLOW_CDEV_IOC_SET, &val)) < 0)
{
printf("ioctl set fail:%s\n", strerror(errno));
}
val = 0;
if ((ret = ioctl(fd, FELLOW_CDEV_IOC_GET, &val)) < 0)
{
printf("ioctl set fail:%s\n", strerror(errno));
}
printf("get val:%d\n", val);
val = 0;
if ((ret = ioctl(fd, FELLOW_CDEV_IOC_PRINT, &val)) < 0)
{
printf("ioctl set fail:%s\n", strerror(errno));
}
close(fd);
return ret;
}
用5中的Makefie编译就会在当前目录下生成fellowcdev.ko
sudo insmod fellowcdev.ko
insmod后,cat /proc/devices就可以看到fellowcdev.ko 加载成功。
要在/dev/下显示设备,需要建立设备节点
sudo mknod /dev/fellowcdv c 199 0
199即为主设备号,与设备驱动里面写的一致。
运行sudo ./app
查看驱动程序log
dmesg | tail -n 10