Linux设备驱动
驱动:能够控制硬件实现特定功能的软件代码就是驱动
ARM裸机驱动和驱动区别?
ARM裸机驱动是不基于操作系统的软件代码,通常这份代码都是有开发者独立编写完成的。
驱动是基于内核(Linux)架构的基础上的软件代码,不但能够操作底层的硬件,还需要和Linux内核对接,通常Linux是内核开发者已经编写的代码,用户只需要编写驱动即可。
一、Linux内核模块
1. 内核模块三要素:
入口:资源申请工作,在驱动安装的时候执行
出口:资源释放工作,写驱动卸载的时候执行
许可证:驱动要遵从GPL协议(开源)
2.内核模块的编写方式:(代码块)
//内核的头文件在内核源码目录下通过vi -t xx查找
//ctags -R 创建索引
//或者在内核目录下 make tags
#include <linux/init.h>
#include <linux/module.h>
//1.入口
static int __init demo_init(void)
{
//static:修饰的函数只能在当前文件中使用
//int:这个函数的返回值类型
//__init:#define __init __section(".init.text")
//vmlinux.lds内核的链接脚本,.init.text这是脚本中的一个段
//内核源码--->vmlinux.lds--->uImage
//将驱动的入口函数都放在.init.text段中
//demo_init:入口函数的名字 led_init uart_init...
//(void) :入口函数没有参数
return 0;
}
//2.出口
static void __exit demo_exit(void)
{
//void类型没有返回值
//__exit告诉编译器将demo_exit函数放在.exit.text段中
}
module_init(demo_init);
//module_init是内核提供的宏
//告诉内核驱动的入口函数的地址
module_exit(demo_exit);
//告诉内核驱动的出口函数的地址
//3.许可证
MODULE_LICENSE("GPL"); //遵从开源协议
3.内核模块的编译方式:
内核模块在编译的时候不能使用gcc直接编译,应为在驱动代码中有依赖内核的源码,
所以驱动的编译要依赖内核,必须使用Makefile才能让驱动编译驱动。
1.内部编译:在内核源码树中进行编译
Kconfig .config Makefile
2.外部编译:在内核源码树外进行编译(自己写通用Makefile文件)
make arch=架构 modname=模块名
arch ?=arm
modname ?=demo
ifeq ($(arch),arm)
#KERNELDIR:指向内核目录的一个变量
KERNELDIR:= /home/ubuntu/linux-5.10.61 #开发板上可安装的arm格式
else
KERNELDIR := /lib/modules/$(shell uname -r)/build/ #ubuntu可以安装的x86-64格式
endif
PWD :=$(shell pwd)
#当前路径$(shell pwd)在Makefile的时候起一个终端
#这个终端上执行pwd,将这个命令的结果赋值给PWD变量
all:
make -C $(KERNELDIR) M=$(PWD) modules
#make -C $(KERNELDIR)
#进入到内核顶层目录下,读取这个目录下的Makefile文件,然后执行make
# M=$(PWD) :指定编译模块的路径为当前路径
# make modules 模块化编译
#进入内核顶层目录下读取Makefile文件,然后进行模块化编译
#通过M指定编译的目录在当前目录
clean:
make -C $(KERNELDIR) M=$(PWD) clean
#清除编译
obj-m:=$(modname).o
#指定编译的当前目录的的模块名是$(modname)===>$(modname).ko
4.内核模块的安装和卸载
sudo insmod xxx.ko //安装内核模块
lsmod //查看内核模块
sudo rmmod xxx //卸载驱动模块
5.内核模块中打印语句的使用printk
5.1 printk的用法格式:
printk(打印级别 "控制格式",参数列表); //指定消息的级别
printk("控制格式",参数列表); //消息的默认级别
注:内核中的printk用法和printf用法除了打印级别之外都是一样的,这个打印级别是用来过滤打印信息
5.2printk过滤信息方式:
消息有消息级别,终端也有终端的级别,只有当消息的级别大于终端的级别的时候消息才会在终端上显示。
cat /proc/sys/kernel/printk
#define KERN_EMERG "0" /* system is unusable */ #define KERN_ALERT "1" /* action must be taken immediately */ #define KERN_CRIT "2" /* critical conditions */ #define KERN_ERR "3" /* error conditions */ #define KERN_WARNING "4" /* warning conditions */ #define KERN_NOTICE "5" /* normal but significant condition */ #define KERN_INFO "6" /* informational */ #define KERN_DEBUG "7" /* debug-level messages */
6.修改消息的默认级别
1.修改ubuntu的默认打印级别
su root
echo 4 3 1 7 > /proc/sys/kernel/printk
2.修改开发板默认打印级别
rootfs/etc/init.d/rcS
在这个脚本的最后一行加上--->echo 4 3 1 7 > /proc/sys/kernel/printk
7. 可以通过命令查看打印信息(主动查看)
dmesg //查看内核从启动到当前这一刻所有的打印信息
sudo dmesg -C或-c //清除打印信息(-C直接清除,-c先回显,在清除)
注:如果是白色的说明消息级别是小于终端级别的,如果是红色的说明消息级别是高于终端级别的
8. 内核模块传参
sudo insmod demo.ko a=10 b=20
通过modinfo xxx.ko看到的现象是:parm: ih:this is backlight var range[0-255] (int)
#include <linux/init.h>
#include <linux/module.h>
int a=20;
module_param(a,int,0664); // module_param(name, type, perm) 用来接收命令行传递过来的参数
//@name:变量名 @type:变量的类型 @perm:权限 (最大的权限是0664)
/*当这里的权限不是0的时候,它会在/sys/module/驱动名字目录/parameters/
目录下产生产生一个以name命名的文件,这个权限就是修饰这个文件权限的*/
//1.入口
static int __init demo_init(void)
{
printk(KERN_ERR "hello DC21121 everyone!!!\n");
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
printk("a = %d\n",a);
return 0;
}
//2.出口
static void __exit demo_exit(void)
{
printk(KERN_ERR "bye DC21121 everyone!!!\n");
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议
注意:
1.传递char类型的成员 <===不能传递字符类型,只能传递整数
2.传递short类型的成员
3.传递字符串指针(char *)成员 <====字符串中不能有空格
sudo insmod 01module_param.ko ch=97 sh=200 ih=900 sp=hello everyone
#include <linux/init.h>
#include <linux/module.h>
char ch='A';
module_param(ch,byte,0664);
short sh=123;
module_param(sh,short,0664);
int ih=2222;
module_param(ih,int,0664);
char *sp = "hell world";
module_param(sp,charp,0664);
//1.入口
static int __init demo_init(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
printk("ch = %d\n",ch);
printk("sh = %d\n",sh);
printk("ih = %d\n",ih);
printk("sp = %s\n",sp);
return 0;
}
//2.出口
static void __exit demo_exit(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
printk("ch = %d\n",ch);
printk("sh = %d\n",sh);
printk("ih = %d\n",ih);
printk("sp = %s\n",sp);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议
查看模块内可传参的变量
modinfo 01module_param.ko
二、字符设备驱动
1.字符设备驱动的架构
2.字符设备驱动相关API
编写字符设备驱动:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#define CNAME "mycdev"
int major;
int mycdev_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t mycdev_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t mycdev_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
//1.注册字符设备驱动
major = register_chrdev(0,CNAME,&fops);
if(major < 0){
printk("register char device driver error\n");
return major;
}
printk("register char device driver success... major = %d\n",major);
return 0;
}
static void __exit mycdev_exit(void)
{
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
3.字符设备驱动的测试流程
1.编译驱动
将驱动的源文件从windows上拷贝到ubuntu上,然后使用Makefile对它进行编译
make arch=x86 modname=mycdev
2.安装驱动
sudo insmod mycdev.ko
dmesg ====>在入口函数中打印的信息
cat /proc/devices ====>主设备号和设备名
3.为驱动创建设备节点
sudo mknod /dev/hello c 240 0
mknod :创建节点的命令
/dev/hello :设备节点的路径和设备节点名(任意路径都可以,习惯上放在/dev/)
c/b : c字符设备 b块设备
240 :主设备号
0 :次设备号(0-255任意一个都可以)
注:设备节点是应用程序访问到驱动的一个文件
4.用户空间和内核空间数据传递
数据拷贝函数API
5.编写LED的驱动:基于STM32MP157单片机
地址映射
应用LED的操作是通过操作寄存器完成,寄存器的地址是物理地址,而驱动运行在3-4G虚拟内存中,所以如果向在内核中操作LED的寄存器,就将LED的寄存器的地址映射到内核空间。
可以通过ioremap/iounmap完成地址的映射和取消映射的过程。
myled.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "myled.h"
#define CNAME "myled"
int major;
char kbuf[128] = {0};
unsigned int *virt_moder;
unsigned int *virt_odr;
unsigned int *virt_rcc;
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t myled_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
//如果用户想写的大小大于内核的内存大小,更正用户写的大小
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_to_user(ubuf,kbuf,size);
if(ret){ //成功返回0,失败返回未拷贝的字节的个数
printk("copy data to user error\n");
return -EIO; //失败返回错误码
}
return size; //成功返回拷贝的字节的个数
}
ssize_t myled_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
//如果用户想写的大小大于内核的内存大小,更正用户写的大小
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if(ret){ //成功返回0,失败返回未拷贝的字节的个数
printk("copy data from user error\n");
return -EIO; //失败返回错误码
}
printk("kernel data = %s\n",kbuf);
if(kbuf[0]=='1'){
*virt_odr |=(1<<10); //high
}else if(kbuf[0]=='0'){
*virt_odr &=~(1<<10); //low
}
return size; //成功返回拷贝的字节的个数
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.release = myled_close,
};
static int __init myled_init(void)
{
//1.注册字符设备驱动
major = register_chrdev(0,CNAME,&fops);
if(major < 0){
printk("register char device driver error\n");
return major;
}
printk("register myled driver success... major = %d\n",major);
//2.映射LED1的地址,并将LED1初始化为熄灭
virt_moder = ioremap(PHY_LED1_MODER,4);
if(virt_moder == NULL){
printk("ioremap moder addr error\n");
return -ENOMEM;
}
virt_odr = ioremap(PHY_LED1_ODR,4);
if(virt_odr == NULL){
printk("ioremap odr addr error\n");
return -ENOMEM;
}
virt_rcc = ioremap(PHY_LED1_RCC,4);
if(virt_rcc == NULL){
printk("ioremap rcc addr error\n");
return -ENOMEM;
}
*virt_rcc |= (1<<4); //rcc enable
*virt_moder &=~(3<<20);
*virt_moder |=(1<<20); //output
*virt_odr &=~(1<<10); //low
return 0;
}
static void __exit myled_exit(void)
{
iounmap(virt_rcc);
iounmap(virt_odr);
iounmap(virt_moder);
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
myled.h
#ifndef __MYLED_H__
#define __MYLED_H__
#define PHY_LED1_MODER 0x50006000
#define PHY_LED1_ODR 0x50006014
#define PHY_LED1_RCC 0x50000a28
#endif
test.c
#include <head.h> // arm-linux-gnueabihf-gcc test.c -I /usr/include
int main(int argc, const char *argv[])
{
int fd;
char buf[128] = {0};
if ((fd = open("/dev/myled", O_RDWR)) == -1)
{
perror("open error");
exit(EXIT_FAILURE);
}
while (1)
{
printf("input 0(off),1(on) > ");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(fd, buf, sizeof(buf));
}
close(fd);
return 0;
}
三、字符设备驱动:udev机制
1. 创建设备节点的机制
- mknod:创建设备节点的命令
- devfs:是早期linux创建设备节点的机制,创建设备节点的逻辑在内核空间(2.4版本之前)
- udev:创建设备节点的逻辑在用户空间,从内核2.6版本以后至今
- mdev:作为uudev机制的轻量级存在,常用于一些嵌入式芯片中
2.udev机制创建设备节点的原理
1.用户在设备驱动中向上提交目录,在内核会申请一个struct class类型的空间,内部存放了 目录信息,内核会在/sys/class/下创建一个目录
2.我们在设备驱动中向上提交节点信息,在内核中会申请一个struct device类型的空间,内 部存放了我们提交的节点信息,内核会在/sys/class/目录/下创建一个存放节点信息的文件
3.完成上面两步后hotplug会通知udev根据/sys/class/目录/存放节点信息的文件中的信息 在/dev下面创建一个设备文件
3.创建设备节点相关API
1.#include<linux/device.h>
struct class * class_create(struct module *owner, const char *name)
功能:向上提交目录信息(在内核中申请一个struct class变量的空间,并初始化)
参数:
owner:指向一个struct module 类型空间的指针,填写THIS_MODULE(指向当前模块的指针)
name:目录名 返回值:成功返回申请成功的struct class空间指针
失败返回错误指针
//内核顶层预留了4K空间,当class_create函数调用失败,得到的返回值是一个指向这4K预留空间的指针 bool __must_check IS_ERR(__force const void *ptr)
功能:用于判断指针是否指向内核顶层预留的4K空间
返回值:在预留空间就返回真,不在就返回假
long __must_check PTR_ERR(__force const void *ptr)
功能:根据错误码指针得到一个错误码的绝对值
2.void class_destroy(struct class *cls);
功能:销毁申请的struct class空间
参数: cls:cls_create申请的空间首地址
返回值:无
3. struct device *device_create (struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
功能:向上提交设备节点信息(申请一个struct device类型的空间)
参数: class:class_create申请的空间首地址
parent:指定当前申请的struct device空间的额父节点指针,填NULL
devt:设备驱动的设备号
MKDEV(major,minor)//根据主设备号和次设备号得到设备号
MAJOR(dev)//根据设备号得到主设备号
MINOR(dev)//根据设备号得到次设备号
drvdata:填充到申请的struct device 空间中的一个私有数据,填NULL即可
fmt:设备节点的名字
返回值:成功返回申请的struct device 空间首地址,失败错误指针
4.void device_destroy(struct class *class, dev_t devt)
功能:销毁申请的到的struct device空间
参数: class:class_create申请的空间首地址
devt:设备号
返回值:无
判断错误码的方法:
注:宏多条语句的用法
#include <head.h>
int max;
#define MAX(a, b) \
do \
{ \
if (a > b) \
max = a; \
else \
max = b; \
} while (0)
//do{}while(0) :可以有多条语句,但是乜有返回值,即使有return也不是宏的返回值
#define MAXX(a, b) ({int tt;if(a>b)tt=a;else tt=b; tt; })
//(())宏:可以有多条语句,最后一句话的结果就是宏的返回值
int main(int argc, const char *argv[])
{
MAX(100, 200);
printf("max = %d\n", max);
printf("max = %d\n",MAXX(1000,200));
return 0;
}
自动创建设备节点的实例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"
#define CNAME "myled"
#define LED1_ON (virt_led1->ODR |=(1<<10))
#define LED1_OFF (virt_led1->ODR &=~(1<<10))
#define LED2_ON (virt_led2->ODR |=(1<<10))
#define LED2_OFF (virt_led2->ODR &=~(1<<10))
#define LED3_ON (virt_led3->ODR |=(1<<8))
#define LED3_OFF (virt_led3->ODR &=~(1<<8))
int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;
struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t myled_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
printk("copy data to user error\n");
return -EIO;
}
return size;
}
//kbuf[2] = {which,status};
//0 led1 1 led2 2 led3
//0 on 1 off
ssize_t myled_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if(ret){
printk("copy data from user error\n");
return -EIO;
}
switch(kbuf[0]){ //kbuf[0] which
case LED1:
//kbuf[1] status
kbuf[1]==1?LED1_ON:LED1_OFF;
break;
case LED2:
kbuf[1]==1?LED2_ON:LED2_OFF;
break;
case LED3:
kbuf[1]==1?LED3_ON:LED3_OFF;
break;
default:
printk("input arg error,try again\n");
return -EINVAL;
}
return size;
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.release = myled_close,
};
int all_led_init(void)
{
virt_led1 = ioremap(PHY_LED1_ADDR,sizeof(gpio_t));
if(virt_led1 == NULL){
printk("ioremap led1 addr error\n");
return -ENOMEM;
}
virt_led2 = ioremap(PHY_LED2_ADDR,sizeof(gpio_t));
if(virt_led2 == NULL){
printk("ioremap led2 addr error\n");
return -ENOMEM;
}
virt_led3 = virt_led1;
virt_rcc = ioremap(PHY_RCC_ADDR,4);
if(virt_rcc == NULL){
printk("ioremap rcc addr error\n");
return -ENOMEM;
}
*virt_rcc |= (3<<4); // rcc gpioe gpiof enable
//init led1
virt_led1->MODER &=~(3<<20);
virt_led1->MODER |=(1<<20); //output
virt_led1->ODR &=~(1<<10); //led1 off
//init led2
virt_led2->MODER &=~(3<<20);
virt_led2->MODER |=(1<<20); //output
virt_led2->ODR &=~(1<<10); //led2 off
//init led3
virt_led3->MODER &=~(3<<16);
virt_led3->MODER |=(1<<16); //output
virt_led3->ODR &=~(1<<8); //led3 off
return 0;
}
static int __init myled_init(void)
{
//1.注册字符设备驱动
major = register_chrdev(0,CNAME,&fops);
if(major < 0){
printk("register char device driver error\n");
return major;
}
printk("register myled driver success... major = %d\n",major);
//2.led地址映射及初始化
all_led_init();
//3.自动创建设备节点
cls = class_create(THIS_MODULE,"hello");
if(IS_ERR(cls)){
printk("class create error\n");
return PTR_ERR(cls);
}
dev = device_create(cls,NULL,MKDEV(major,0),NULL,"myled");
if(IS_ERR(dev)){
printk("device create error\n");
return PTR_ERR(dev);
}
return 0;
}
static void __exit myled_exit(void)
{
device_destroy(cls,MKDEV(major,0));
class_destroy(cls);
iounmap(virt_rcc);
iounmap(virt_led1);
iounmap(virt_led2);
unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
四、字符设备驱动的内部实现
1.字符设备驱动框架图
文件存在文件系统中,会有一个标识inode号,基于这个标识找到了struct_inode结构体(保存当前文件信息),struct_inode结构体中有一个struct cdev *i_cdev类型的字符设备指针,这个指针指向当前驱动对象struct cdev结构体 (字符设备驱动对象结构体)/(设备号是驱动存在内核的标识,也是设备驱动和设备文件关联的纽带),而这个结构体存放有struct file_operations *ops 操作方法结构体指针;接着基于这个struct file_operations *ops 操作方法结构体指针,找到了操作方法 mycdev_open()、mycdev_read()、mycdev_write()、mycdev_close();操作方法回调到mycdev_open(用户层)
————————————————
2.字符设备驱动分步实现流程(API)
#include <linux/cdev.h>
1.字符设备驱动结构体
struct cdev {
struct module *owner; //THIS_MODULE
const struct file_operations *ops; //操作方法结构体
struct list_head list; //构成链表
dev_t dev; //设备号
unsigned int count; //设备的个数
};2.分配字符设备驱动的对象
struct cdev cdev;
struct cdev *cdev = cdev_alloc();
struct cdev *cdev_alloc(void)
功能:为cdev结构体指针分配内存
参数:
@无
返回值:成功返回cdev的结构体指针,失败返回NULL
3.字符设备驱动对象初始化
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
功能:完成cdev结构体成员的初始化(部分)
参数:
@cdev:cdev的结构体指针
@fops:操作方法结构体指针
返回值:无
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:静态指定设备号
参数:
@from:指定的设备号 (主设备号|次设备号 eg: 241<<20|0)
@count:设备的个数(3)
@name:设备驱动的名字 cat /proc/devices查看
返回值:成功返回0,失败返回错误码
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
功能:动态申请设备号
参数:
@dev :申请到的设备号
@baseminor:次设备号开始值
@count:设备的个数(3)
@name:设备驱动的名字 cat /proc/devices查看
返回值:成功返回0,失败返回错误码
4.注册字符设备驱动
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
功能:注册字符设备驱动
参数:
@p:cdev结构体指针
@dev:设备号
@count:设备的个数
返回值:成功返回0,失败返回错误码
-------------------------------------------------------------------------------------
1.销毁字符设备驱动
void cdev_del(struct cdev *p)
功能:销毁字符设备驱动
参数:
@p:cdev结构体指针
返回值:无
2.释放设备号
void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
参数:
@from:设备号
@count:设备的个数
返回值:无
3.释放动态申请的内存
void kfree(void *p)
功能:释放动态申请的内存
参数:
@p:cdev结构体的首地址
返回值:无
分步实现字符设备驱动的实例
基础框架
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
//定义cdev的结构体指针变量
struct cdev *cdev;
static int __init mycdev_init(void)
{
//1.分配对象
//2.对象的初始化
//3.申请设备号
//4.字符设备驱动的注册
//5.自动创建设备节点
return 0;
}
static void __exit mycdev_exit(void)
{
//1.销毁设备节点
//2.销毁字符设备驱动
//3.销毁设备号
//4.释放动态申请的cdev内存
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
mycdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define CNAME "mycdev"
//定义cdev的结构体指针变量
struct cdev *cdev;
#if 0
unsigned int major = 0; //动态申请
#else
unsigned int major = 500; //静态指定
#endif
int minor=0;
const int count=3;
struct class *cls;
struct device *dev;
char kbuf[128] = {0};
int mycdev_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t mycdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
printk("copy data to user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if(ret){
printk("copy data from user error\n");
return -EIO;
}
return size;
}
int mycdev_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int i,ret;
dev_t devno;
//1.分配对象
cdev = cdev_alloc();
if(cdev == NULL){
printk("cdev alloc memory error\n");
ret = -ENOMEM;
goto ERR1;
}
//2.对象的初始化
cdev_init(cdev,&fops);
//3.申请设备号
if(major == 0){
//动态申请
ret = alloc_chrdev_region(&devno,minor,count,CNAME);
if(ret){
printk("dynamic:alloc device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}else if(major > 0){
//静态指定
ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
if(ret){
printk("static:alloc device number error\n");
goto ERR2;
}
}
//4.字符设备驱动的注册
ret = cdev_add(cdev,MKDEV(major,minor),count);
if(ret){
printk("cdev register error\n");
goto ERR3;
}
//5.自动创建设备节点
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for(i=0;i<count;i++){
dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
if(IS_ERR(dev)){
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
return 0; //!!!!!!!这里的return 0千万不要忘记写!!!!!!!!!!!!!!
ERR5:
for(--i;i>=0;i--){
device_destroy(cls,MKDEV(major,i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major,minor),count);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
//1.销毁设备节点
for(i=0;i<count;i++){
device_destroy(cls,MKDEV(major,i));
}
class_destroy(cls);
//2.销毁字符设备驱动
cdev_del(cdev);
//3.销毁设备号
unregister_chrdev_region(MKDEV(major,minor),count);
//4.释放动态申请的cdev内存
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
五、Linux系统中 ioctl函数的使用
1. ioctl的函数API
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
功能:控制设备
参数:
@fd ;文件描述符
@request:命令码
@... :可写,可不写,如果填写填写地址
返回值:成功返回0,失败返回-1置位错误码 user
————————————————————————————————————————————
struct file_operations kernel
long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg)
{
//应用层的request传递给cmd
//应用层的...传递给arg
}
2. ioctl函数的命令码
====== ==================================
bits meaning
====== ==================================
31-30 00 - no parameters: uses _IO macro
10 - read: _IOR
01 - write: _IOW
11 - read/write: _IOWR
29-16 size of arguments
15-8 ascii character supposedly
unique to each driver
7-0 function #
====== ==================================
如果向得到上述的32位表述某种功能的命名码需要通过如下的宏实现
3. ioctl的实例
myled.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"
#define CNAME "myled"
#define LED1_ON (virt_led1->ODR |= (1 << 10))
#define LED1_OFF (virt_led1->ODR &= ~(1 << 10))
#define LED2_ON (virt_led2->ODR |= (1 << 10))
#define LED2_OFF (virt_led2->ODR &= ~(1 << 10))
#define LED3_ON (virt_led3->ODR |= (1 << 8))
#define LED3_OFF (virt_led3->ODR &= ~(1 << 8))
int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;
struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t myled_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret)
{
printk("copy data to user error\n");
return -EIO;
}
return size;
}
// kbuf[2] = {which,status};
// 0 led1 1 led2 2 led3
// 0 on 1 off
ssize_t myled_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret)
{
printk("copy data from user error\n");
return -EIO;
}
return size;
}
long myled_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
//int which, ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
switch(cmd){
case LED1ON:
LED1_ON;
break;
case LED1OFF:
LED1_OFF;
break;
}
// switch (cmd)
// {
// case LED_ON:
// ret = copy_from_user(&which, (void *)arg, sizeof(int));
// if (ret)
// {
// printk("copy data from user error\n");
// return -EINVAL;
// }
// switch (which)
// {
// case LED1:
// LED1_ON;
// break;
// case LED2:
// LED2_ON;
// break;
// case LED3:
// LED3_ON;
// break;
// }
// break;
// case LED_OFF:
// ret = copy_from_user(&which, (void *)arg, sizeof(int));
// if (ret)
// {
// printk("copy data from user error\n");
// return -EINVAL;
// }
// switch (which)
// {
// case LED1:
// LED1_OFF;
// break;
// case LED2:
// LED2_OFF;
// break;
// case LED3:
// LED3_OFF;
// break;
// }
// break;
// break;
// }
return 0;
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.unlocked_ioctl = myled_ioctl,
.release = myled_close,
};
int all_led_init(void)
{
virt_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
if (virt_led1 == NULL)
{
printk("ioremap led1 addr error\n");
return -ENOMEM;
}
virt_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
if (virt_led2 == NULL)
{
printk("ioremap led2 addr error\n");
return -ENOMEM;
}
virt_led3 = virt_led1;
virt_rcc = ioremap(PHY_RCC_ADDR, 4);
if (virt_rcc == NULL)
{
printk("ioremap rcc addr error\n");
return -ENOMEM;
}
*virt_rcc |= (3 << 4); // rcc gpioe gpiof enable
// init led1
virt_led1->MODER &= ~(3 << 20);
virt_led1->MODER |= (1 << 20); // output
virt_led1->ODR &= ~(1 << 10); // led1 off
// init led2
virt_led2->MODER &= ~(3 << 20);
virt_led2->MODER |= (1 << 20); // output
virt_led2->ODR &= ~(1 << 10); // led2 off
// init led3
virt_led3->MODER &= ~(3 << 16);
virt_led3->MODER |= (1 << 16); // output
virt_led3->ODR &= ~(1 << 8); // led3 off
return 0;
}
static int __init myled_init(void)
{
// 1.注册字符设备驱动
major = register_chrdev(0, CNAME, &fops);
if (major < 0)
{
printk("register char device driver error\n");
return major;
}
printk("register myled driver success... major = %d\n", major);
// 2.led地址映射及初始化
all_led_init();
// 3.自动创建设备节点
cls = class_create(THIS_MODULE, "hello");
if (IS_ERR(cls))
{
printk("class create error\n");
return PTR_ERR(cls);
}
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "myled");
if (IS_ERR(dev))
{
printk("device create error\n");
return PTR_ERR(dev);
}
return 0;
}
static void __exit myled_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
iounmap(virt_rcc);
iounmap(virt_led1);
iounmap(virt_led2);
unregister_chrdev(major, CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
myled.h
#ifndef __MYLED_H__
#define __MYLED_H__
typedef struct{
volatile unsigned int MODER;
volatile unsigned int OTYPER;
volatile unsigned int OSPEEDR;
volatile unsigned int PUPDR;
volatile unsigned int IDR;
volatile unsigned int ODR;
volatile unsigned int BSRR;
}gpio_t;
#define PHY_RCC_ADDR 0x50000a28
#define PHY_LED1_ADDR 0x50006000
#define PHY_LED2_ADDR 0x50007000
#define PHY_LED3_ADDR 0x50006000
enum{
LED1,
LED2,
LED3
};
#define LED_ON _IOW('a',0,int)
#define LED_OFF _IOW('a',1,int)
#define LED1ON _IO('a',3)
#define LED1OFF _IO('a',4)
#endif
test.c
#include <head.h>
#include "myled.h"
int main(int argc, const char *argv[])
{
int fd;
int which;
if ((fd = open("/dev/myled", O_RDWR)) == -1)
{
perror("open error");
exit(EXIT_FAILURE);
}
while (1)
{
// which = LED1;
// ioctl(fd, LED_ON, &which);
// sleep(1);
// ioctl(fd, LED_OFF, &which);
// sleep(1);
ioctl(fd, LED1ON);
sleep(1);
ioctl(fd, LED1OFF);
sleep(1);
}
close(fd);
return 0;
}