需要板子一起学习的可以这里购买(含资料):点击跳转
一、 linux设备驱动分类
1、字符设备---char
应用程序与驱动程序在进行数据传输时,数据以"字节"为单位。
特点:
[1] 按照顺序进行数据传输
[2] 数据传输是实时的,并没有缓存
[3] 字符设备是没有文件系统的
1)驱动程序的作用:是应用程序访问底层硬件设备的一座桥梁
2)应用程序如何去访问驱动程序:系统调用
应用程序:系统IO(open、read、write、ioctl、close)
驱动程序: 文件操作集---> struct file_operations
3)驱动程序如何去访问底层硬件 ---> MMU
驱动程序中的地址为虚拟地址 ----> MMU(io内存映射) ---->底层硬件的物理地址
虚拟地址----->是通过在驱动程序中,来申请的虚拟地址
4)什么设备叫作字符设备呢?
大多数的设备都是字符设备,比如:led, lcd, beep, 按键, SPI等
2、块设备---block
应用程序与驱动程序在进行数据传输时,数据以"块"为单位,1block = 1024kb。
特点:
[1]有缓存
[2]有文件系统,比如:NTFS ,ext4
大容量设备都是块设备:U盘,SD卡,EMMC--电子硬盘
[root@GEC6818 /mnt]#cat /proc/partitions
major minor #blocks name179 0 7634944 mmcblk0 ---->电子硬盘179 1 65536 mmcblk0p1 -----> 电子硬盘第一个分区179 2 772096 mmcblk0p2179 3 438272 mmcblk0p3179 4 1 mmcblk0p4179 5 8192 mmcblk0p5179 6 22528 mmcblk0p6179 7 6324224 mmcblk0p7//u盘
[root@GEC6818 /dev]#ls -lh sda*
brw-rw-rw- 1 root root 8, 0 Jan 1 00:30 sda ---> 占1G
brw-rw-rw- 1 root root 8, 1 Jan 1 00:30 sda1 ----> 31G
应用程序如何访问块设备:
第一步:挂载U盘到根文件系统上
[root@GEC6818 /]#mount -t vfat /dev/sda1 /udata
第二步:像访问普通文件一样来访问块设备的内容
标准的IO函数
3、网络设备----socket
网卡类的设备:有线网卡、无线网卡 ...,另外,网络设备是没有设备文件的
应用程序:
socket套接字:根据ip地址 + 端口号
二、字符设备驱动设计流程
以led灯为例
1、定义并初始一个字符设备(struct cdev),在驱动程序中对设备的描述和控制:
struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};
比如:struct cdev led_cdev;
2、根据应用程序的API来定义并且初始化字符设备的文件操作集(struct file_operations).
3、给字符设备申请一个设备号 --> 主设备号<<20 + 次设备号.
4、初始化字符设备.
5、把当前的字符设备加入到内核.
6、创建class.
7、创建设备(device)。其中,device属于class,应用程序可以通过设备文件来访问驱动程序.
8、申请物理内存区,申请SFR(Special Function Register,特殊功能寄存器)地址区,比如:GPIOEOUT,GPIOEOUTENB
9、内存的动态映射,得到物理地址的虚拟地址.
10、在驱动程序中,访问虚拟地址.
具体实现如下:
1)定义一个字符设备(struct cdev)
#include <linux/cdev.h>
struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops;struct list_head list;dev_t dev;unsigned int count;
};关于struct cdev结构体的说明:
1)struct kobject kobj; ---->内核管理驱动时,使用的一个object对象,内核自己使用,跟驱动程序设计者无关
2)struct module *owner; ----> 模块的拥有者,当前驱动设计模块是属于谁的,固定写法:THIS_MODULE
3)const struct file_operations *ops; ---> cdev的文件操作集一般由用户或者设计者来实现static struct file_operations gec6818_led_ops;
4)struct list_head list; --->管理字符设备的链表,由内核本身去负责
5)dev_t dev; ---->设备号
6)unsigned int count; ---->字符设备的次设备号的数目
在linux内核中,使用cdev来描述字符设备,每个字符设备都有一个自己的cdev,比如:
static struct cdev gec6818_led;
2) 定义一个字符设备的文件操作集(struct file_operations)
#include <linux/fs.h>
struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
};根据文件操作集中的函数指针定义相关类型的函数:函数的格式是固定
int gec6818_led_release(struct inode *node, struct file *file)
{printk("gec6818_led_release\n");return 0;
}
static struct file_operations gec6818_led_ops = {.owner = THIS_MODULE,.open = gec6818_led_open,.write = gec6818_led_write,.release = gec6818_led_release,//当应用程序调用close时,执行驱动程序中的接口函数
};
3) 给设备申请一个设备号---dev_t
1、什么是设备号
每个设备文件(字符设备or块设备)都有一个设备号,相当于设备文件的ID.
设备号由主设备和次设备号组成.
dev_t dev; ---> 字符设备的设备号
/*
typedef __kernel_dev_t dev_t;
typedef __u32 __kernel_dev_t; ---->无符号整型数字
*/
2、设备号的运算函数
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi)) ----> 设备号
dev = MKDEV(ma,mi); //根据主设备号与次设备号得到设备号
3、得到主设备号与次设备号
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS)) ----> 主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK)) ----> 次设备号
unsigned int major = MAJOR(dev); //根据设备号得到主设备号
unsigned int minor = MINOR(dev); //根据设备号得到次设备号
4、把得到的设备号注册到内核中
一) 静态注册:由用户先确定一个设备号,通过注册函数去申请
int register_chrdev_region(dev_t from, unsigned count, const char *name)
参数说明:dev_t from ----> 要申请的设备号unsigned count ---> 次设备号的数目const char *name ----> 设备名字 --->字符串
返回值:成功:0失败: <0
比如:register_chrdev_region(108,4,"ttyGS");
二) 动态注册:由内核动态分配一个设备号给现有设备
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
参数说明:dev_t *dev --->做为输出参数,通过alloc_chrdev_region得到的设备号给devunsigned baseminor -->第一个次设备号unsigned count -->次设备号数目const char *name ----> 设备名字 --->字符串
返回值:成功:0失败: <0
5、设备号的作用
主设备号:描述一个硬件是属于哪一类的设备,比如:uart,lcd,摄像头
次设备号:描述某种类型的设备中具体的哪一个设备,比如:uart0,uart1
crw-rw---- 1 root root 247, 0 Jan 1 1970 ttyGS0
crw-rw---- 1 root root 247, 1 Jan 1 1970 ttyGS1
crw-rw---- 1 root root 247, 2 Jan 1 1970 ttyGS2
crw-rw---- 1 root root 247, 3 Jan 1 1970 ttyGS3247 ---->主设备号 ----->表示串口
0 ----->次设备号 -----> 串口1
ttyGS -----> 表示该硬件的设备文件,如果该类型的设备有多个,由从0开始,由系统自动加1
6、释放设备号给系统
void unregister_chrdev_region(dev_t from, unsigned count)
参数说明:dev_t from ----> 要申请的设备号unsigned count -->次设备号数目
4) 初始化字符设备
#include <linux/cdev.h>
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数说明:struct cdev *cdev -----> 字符设备的结构const struct file_operations *fops ----> 字符设备对应的文件操作集
5) 把设备加入到内核中
#include <linux/cdev.h>
//把字符设备增加到系统
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数说明:struct cdev *cdev -----> 字符设备的结构dev_t dev -----> 字符设备的设备号unsigned count ----> 次设备号数目
失败:返回一个负错误码//从系统中,把字符设备删除
void cdev_del(struct cdev *p)
6) 创建class,一个设备会有一个class
[root@GEC6818 /sys/class]#ls ---->查看设备的class
android_usb graphics leds pvrusb2 spi_master
arvo hwmon lirc pyra spidev
axppower i2c-adapter mdio_bus rc switch
backlight i2c-dev mem regulator timed_output//创建class
#include <linux/device.h>
struct class *class_create(struct module *owner, const char *name)
参数说明:struct module *owner ----> 模块的拥有者const char *name -----> class的名字 ----> /sys/class--->查看返回值成功:&struct class pointer ----class的地址失败:NULL//销毁class
void class_destroy(struct class *cls)
7) 创建device
//创建一个device
#include <linux/device.h>
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
参数说明:struct class *class ----> 说明device是属于哪 一个class,class_create返回值struct device *parent ----> 该设备的父亲,一般设置为NULLdev_t devt -----> 增加到系统中的字符设备的设备号void *drvdata ----> 增加一个device时,返回的数值,如果不需要返回,则使用NULLconst char *fmt ----> 该设备的名字返回值:成功:struct device *失败:NULL//销毁device
void device_destroy(struct class *class, dev_t devt)
8) 申请物理内存区,申请SFR地址区
//申请物理内存区
struct resource *request_mem_region(resource_size_t start, resource_size_t n,const char *name)
参数说明:resource_size_t start -----> 虚拟内存的起始地址resource_size_t n ----> 申请虚拟内存的大小const char *name ----> 虚拟内存对应的名字返回值成功:struct resource *失败:NULL//释放物理内存区
void release_mem_region(resource_size_t start, resource_size_t n)
9) IO动态映射
//IO动态映射 ---- 根据物理地址得到虚拟的起始地址
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
参数说明:phys_addr_t offset ---- >物理地址的起始地址unsigned long size -----> IO映射的大小//释放IO映射
static inline void iounmap(void __iomem *addr)
10) 测试
1 插入和删除驱动
[root@GEC6818 /6818_driver]#insmod led_drv.ko
[ 173.611000] gec6818_led_init
[root@GEC6818 /6818_driver]#rmmod led_drv.ko
[ 220.248000] gec6818led_exit2 查看/dev下设备文件、主设备号、次设备
[root@GEC6818 /dev]#ls -l gec6818_led_device
crw-rw---- 1 root root 100, 0 Jan 1 02:46 gec6818_led_device //提供给应用程序来访问驱动程序3 查看class
[root@GEC6818 /sys/class]#ls -l gec6818_led_class/
total 0
lrwxrwxrwx 1 root root 0 Jan 1 02:51 gec6818_led_device
觉得有帮助的话,打赏一下呗。。