一、复习

1.1、字符设备驱动编写

  • alloc_chrdev_region/register_chrdev_region
  • cdev_alloc
  • cdev_init
  • cdev_add
  • class_create
  • device_create
    注意:错误处理,goto语句
    卸载的时候:释放申请的资源,并删除注册的结构体

1.2、设备文件创建

  • mknod
  • 通过udev/mdev创建设备文件(根据uevent文件中的设备文件信息创建)
  • 一个驱动对应驱动多个同类型设备:cdev_demo0、cdev_demo1、cdev_demo2,而应用层打开设备文件,在驱动中,怎么区别应用层打开的是哪一个?
  • 通过次设备号进行区分—>>>怎么去读出次设备号?
  • 应用层open(设备文件名)—系统调用—>>> fops -> open(struct inode *,struct file *)
  • inode结构体中的设备号成员变量区分

二、open的系统调用过程

2.1、应用层

设备文件是创建的:根据设备文件名,设备号,系统在创建文件的时候会创建一个结构体,描述被创建的文件的所有信息:inode 结构体

struct inode{
dev_t i_rdev;
union{
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev*i_cdev;
};
}

在一个进程中调用open(),通过系统调用接口,进入内核层。​​task_struct​​结构体,描述的是进程所有的信息。

struct task_struct{
...
/* files information which opened by task */
struct files_struct *files; //struct file *fd_array[NR_OPEN_DEFAULT];
...
}

同理,打开文件的时候,系统会创建一个​​file​​结构体描述被打开的文件的所有信息,这个结构体指针被存在当前进程维护的一个fd_array[NR_OPEN_DEFAULT]数组中,存的位置就是打开文件的fd值。系统为当前进程task默认打开的标准输入、标准输出和标准错误输出文件占用了数组的前3项0、1、2:

strcut file{
...
unsigned int f_flags;
const struct file_operations *f_op;
...
}

2.2、open系统调用

  • 打开/fs/open.c文件:
  • ​SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)​
  • ​do_sys_open(AT_FDCWD, filename, flags, mode);​
  • ​struct file *f = do_filp_open(dfd, tmp, &op, lookup);​
  • ​filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);​
  • ​do_last(nd, &path, op, pathname);​
  • ​filp = nameidata_to_filp(nd);​
  • ​filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp, NULL, cred);​
  • ​f->f_op = fops_get(inode->i_fop);​
if(!open && f->f_op)
open = f->f_op->open;
if(open){
error = open(inode,f);
}

三、文件接口——操作方法集

3.1、read/write

struct file_operations{
ssize_t (*read)(struct file*,char __user*,size_t,loff_t*);
ssize_t (*write)(struct file*,constchar __user*,size_t,loff_t*);
...
}
  • **读/写:**对用户层来讲,用户读(从内核空间拷贝数据到用户空间),用户写(从用户空间拷贝数据到内核层)。
  • 使用的内核空间和用户空间数据拷贝函数:
/*
*@to: 内核空间缓存区地址
*@from:用户空间缓存器地址
*@n:拷贝的字节数
*/
//从用户层拷贝数据,在驱动中的write接口中调用
int copy_from_user(void *to,const void __user *from,int n);

/*
*@to: 用户空间缓存区地址
*@from:内核空间缓存器地址
*@n:拷贝的字节数
*/
//拷贝给用户,在驱动中的read接口中调用
int copy_to_user(void __user *to,const void *from,int n);

3.2、读写例程

#include <linux/init.h>
#include<linux/module.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/uaccess.h>

#defineNAME"cdev_demo"
#defineCOUNT3
#defineKBUFSIZE64

dev_t dev_no;
struct cdev*cdevp=NULL;
structclass*cls=NULL;
struct device*devp=NULL;

char Kbuf[KBUFSIZE]={'\0'};
int Kbufcount=0;

static int demo_open(struct inode *inode,struct file *filp)
{
printk(KERN_DEBUG"[%s-%s-%d]:runned...\n",__FILE__,__func__,__LINE__);
return 0;
}

static int demo_release(struct inode *inode, struct file *filp)
{
printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",\
__FILE__,__func__,__LINE__);
return 0;
}

ssize_t demo_read(struct file *filp, char __user *buf, size_t size,loff_t *pos)
{
if(size > Kbufcount){
size = Kbufcount;
}
if(copy_to_user(buf,Kbuf,size)){
printk(KERN_ERR"[%s-%s-%d]: copy_to_user failed...\n",\
__FILE__,__func__,__LINE__);
return-EAGAIN;
}
Kbufcount=0;
printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",\
__FILE__,__func__,__LINE__);
return size;
}

ssize_t demo_write(struct file *filp, const char __user *buf, size_t size,loff_t *pos)
{
if(size > KBUFSIZE){
size = KBUFSIZE;
}
if(copy_from_user(Kbuf,buf,size)){
printk(KERN_ERR"[%s-%s-%d]: copy_from_user failed...\n",\
__FILE__,__func__,__LINE__);
return -EAGAIN;
}
Kbufcount = size;
printk(KERN_DEBUG"[%s-%s-%d]: Kbuf:%s...\n",\
__FILE__,__func__,__LINE__,Kbuf);
return size;
}

struct file_operations fops={
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
.read = demo_read,
.write = demo_write,
};

static int __initdemo_init(void)
{
int ret=0,i=0;
//0、申请设备号
ret = alloc_chrdev_region(&dev_no,0,COUNT,NAME);
if(ret<0){
printk(KERN_ERR"[%s-%s-%d]:alloc_chrdev_region failed...\n",\
__FILE__,__func__,__LINE__);
goto err0;
}
printk(KERN_DEBUG"[%s-%s-%d]:devno->major:%d--minor:%d--...\n",\
__FILE__,__func__,__LINE__,MAJOR(dev_no),MINOR(dev_no));

//1、分配cdev结构体
cdevp = cdev_alloc();
if(cdevp == NULL){
printk(KERN_ERR"[%s-%s-%d]:cdev_alloc failed...\n",\
__FILE__,__func__,__LINE__);
ret = -ENOMEM;
goto err1;
}

//2、初始化cdev结构体
cdev_init(cdevp,&fops);

//3、添加到内核中,由内核统一管理
ret = cdev_add(cdevp,dev_no,COUNT);
if(ret<0){
goto err1;
}

//4、class create
cls = class_create(THIS_MODULE,NAME);
if(IS_ERR(cls)){
printk(KERN_ERR"[%s-%s-%d]:class_create...\n",\
__FILE__,__func__,__LINE__);
ret = PTR_ERR(cls);
goto err2;
}

//5、device create
for(i=0; i < COUNT; i++){
devp = device_create(cls,NULL,MKDEV(MAJOR(dev_no),i),\
NULL,"%s%d",NAME,i);
if(IS_ERR(devp)){
printk(KERN_ERR"[%s-%s-%d]:device_create[%d]...\n",\
__FILE__,__func__,__LINE__,i);
ret=PTR_ERR(devp);
goto err3;
}
}
return 0;
err3:
for(--i;i>=0;i--){
device_destroy(cls,MKDEV(MAJOR(devno),i));
}
class_destroy(cls);
err2:
cdev_del(cdevp);
err1:
unregister_chrdev_region(dev_no,COUNT);
err0:
return ret;
}

static void __exit demo_exit(void)
{
int i=0;
for(i=0;i < COUNT;i++){
device_destroy(cls,MKDEV(MAJOR(dev_no),i));
}
class_destroy(cls);
//cdev从内核中删除
cdev_del(cdevp);
//设备号资源释放
unregister_chrdev_region(dev_no,COUNT);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

使用外部编译的方法进行编译,生成内核模块:make (KDIR路径:根据自己的路径修改)

KDIR:=/home/edu/SAMBA_SHARE/BK2101/Driver/03_kernel/kernel-3.4.39
#KDIR:=/usr/src/linux-headers-4.4.0-203-generic

PWD:=$(shell pwd)
obj-m+=demo.o

modules:
make-C$(KDIR)M=$(PWD) modules

clean:
make-C$(KDIR)M=$(PWD) clean

加载执行(根据编译的架构,放在对应的平台上执行)

# insmod demo.ko
# echo"aaaaa" > /dev/cdev_demo0(open ---> write ---> close)
# cat /dev/cdev_demo0(open ---> read ---> close)
# dmesg

自己写应用层代码:调用read/wirte函数

3.2、ioctl接口

NAME
ioctl- control device

SYNOPSIS
#include<sys/ioctl.h>

int ioctl(int fd,unsigned long request,...);

​long (*unlocked_ioctl) (struct file *, unsigned cmd, unsigned args);​

3.2.1、内核提供的封装命令宏函数

dir size   type nr
30 16 8 0

#define _IOC(dir,type,nr,size)\
(((dir) << _IOC_DIRSHIFT)|\
((type) << _IOC_TYPESHIFT)|\
((nr) << _IOC_NRSHIFT)|\
((size) << _IOC_SIZESHIFT))

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
/*used to decode ioctl numbers.. */
/*从命令码中提取DIR、TYPE、NR、SIZE */

#define _IOC_DIR(cmd) (((cmd)>>_IOC_DIRSHIFT)&_IOC_DIRMASK)
#define _IOC_TYPE(cmd) (((cmd)>>_IOC_TYPESHIFT)&_IOC_TYPEMASK)
#define _IOC_NR(cmd) (((cmd)>>_IOC_NRSHIFT)&_IOC_NRMASK)
#define_IOC_SIZE(cmd) (((cmd)>>_IOC_SIZESHIFT)&_IOC_SIZEMASK)

3.2.2、例程

头文件:

#ifndef _IOCTL_DEMO_H
#define _IOCTL_DEMO_H
#include <asm-generic/ioctl.h>

enum demo_speep{
speed_1 = 0,
speed_2,
speed_3,
};

#define CMD_ON _IO('k',0)
#define CMD_OFF _IO('k',1)
#define CMD_SPEED _IOW('k',2,int)
#endif

驱动层源文件:

#include <linux/init.h>
#include<linux/module.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/uaccess.h>
#include"ioctl_demo.h"

#define NAME "cdev_demo"
#define COUNT 3
#define KBUFSIZE 64

dev_t dev_no;
struct cdev *cdevp = NULL;
struct class *cls = NULL;
struct device *devp = NULL;

static int demo_open(struct inode*inode,struct file*filp)
{
printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",__FILE__,__func__,__LINE__);
return0;
}

static int demo_release(struct inode*inode,struct file*filp)
{
printk(KERN_DEBUG"[%s-%s-%d]: runned...\n",\
__FILE__,__func__,__LINE__);
return 0;
}

static long demo_ioctl(struct file*filp,unsigned int cmd,unsigned long args)
{
switch(cmd){
case CMD_ON:
printk(KERN_DEBUG"CMD_ON...\n");
break;
case CMD_OFF:
printk(KERN_DEBUG"CMD_OFF...\n");
break;
case CMD_SPEED:
printk(KERN_DEBUG"CMD_SPEED...\n");
printk(KERN_DEBUG"speed:%lu\n",args);
break;
default:
break;
}

/*
printk(KERN_DEBUG"[%s-%s-%d]: ---cmd:%u---args:%lu----\n",\
__FILE__,__func__,__LINE__,cmd,args);
*/

return 0;
}

struct file_operations fops={
.owner = THIS_MODULE,
.open = demo_open,
.release = demo_release,
.unlocked_ioctl=demo_ioctl,
};

static int __initdemo_init(void)
{
int ret=0,i=0;
//0、申请设备号
ret = alloc_chrdev_region(&dev_no,0,COUNT,NAME);
if(ret<0){
printk(KERN_ERR"[%s-%s-%d]:alloc_chrdev_region failed...\n",\
__FILE__,__func__,__LINE__);
goto err0;
}
printk(KERN_DEBUG"[%s-%s-%d]:devno->major:%d--minor:%d--...\n",\
__FILE__,__func__,__LINE__,MAJOR(dev_no),MINOR(dev_no));
//1、分配cdev结构体
cdevp = cdev_alloc();
if(cdevp==NULL){
printk(KERN_ERR"[%s-%s-%d]:cdev_alloc failed...\n",\
__FILE__,__func__,__LINE__);
ret=-ENOMEM;
goto err1;
}

//2、初始化cdev结构体
cdev_init(cdevp,&fops);
//3、添加到内核中,由内核统一管理
ret = cdev_add(cdevp,dev_no,COUNT);
if(ret<0){
goto err1;
}

//4、class create
cls=class_create(THIS_MODULE,NAME);
if(IS_ERR(cls)){
printk(KERN_ERR"[%s-%s-%d]:class_create...\n",\
__FILE__,__func__,__LINE__);
ret=PTR_ERR(cls);
goto err2;
}

//5、device create
for(i=0;i<COUNT;i++){
devp=device_create(cls,NULL,MKDEV(MAJOR(dev_no),i),NULL,"%s%d",NAME,i);
if(IS_ERR(devp)){
printk(KERN_ERR"[%s-%s-%d]:device_create[%d]...\n",\
__FILE__,__func__,__LINE__,i);
ret=PTR_ERR(devp);
goto err3;
}
}
return 0;
err3:
for(--i;i>=0;i--){
device_destroy(cls,MKDEV(MAJOR(dev_no),i));
}
class_destroy(cls);
err2:
cdev_del(cdevp);
err1:
unregister_chrdev_region(dev_no,COUNT);
err0:
return ret;
}


static void __exit demo_exit(void)
{
int i=0;
for(i=0;i<COUNT;i++){
device_destroy(cls,MKDEV(MAJOR(dev_no),i));
}
class_destroy(cls);

//cdev从内核中删除
cdev_del(cdevp);
//设备号资源释放
unregister_chrdev_region(dev_no,COUNT);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

应用层源文件:

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

#include"ioctl_demo.h"

int main(int argc,constchar*argv[])
{
int fd=open("/dev/cdev_demo2",O_RDWR);
if(fd<0){
perror("open");
}

ioctl(fd,CMD_ON);
ioctl(fd,CMD_OFF);
ioctl(fd,CMD_SPEED,speed_2);
close(fd);
return 0;
}

编译内核模块,和应用层C文件,加载内核模块到操作系统中,执行应用层程序,使用dmesg打印内核信息,进行对比


四、访问硬件

操作系统运行起来后,访问的是虚拟地址,我们在数据手册中读到的是设备的物理地址,所以我们需要通过物理地址,获取到对应的虚拟地址。

/*功能:物理地址映射成虚拟地址
*@offset 物理地址
*@size 映射的字节数
*返回值:虚拟地址
*/

void__iomem*ioremap(phys_addr_t offset,unsigned long size)

/*
*功能解映射
*@addr虚拟地址
*/

void iounmap(void__iomem*addr)

readl(c) //读地址中的数据(4byte), @c虚拟地址
writel(v,c) //写数据到制定的地址(4byte),@v写入的值@c虚拟地址