文章目录
- 一、概念
- (一)相关概念
- (二)字符设备框架结构
- (三)用户空间和内核空间数据传输
- 1. 函数的参数对应关系
- (四)字符设备相关的API
- 1. 字符设备驱动
- (1)注册字符设备驱动
- (2)销毁字符设备驱动
- 2. 用户和内核数据传输
- (1)copy_to_user
- (2)copy_from_user
- 3. 物理地址映射虚拟地址
- (1)地址映射
- (2)取消地址映射
- 二、驱动代码示例
- (一)查看芯片手册
- 1. 寄存器基地址
- (一)驱动代码
- (二)测试
一、概念
(一)相关概念
设备号:内核识别驱动的唯一的编号,设备号由32个bit位组成
设备号(32bit)=主设备号(高12bit)+次设备号(低20bit)
主设备号:代表的是哪一类设备
次设备号:代表同类中的哪一个设备
(二)字符设备框架结构
当用户在应用层面去调用open/read/write/close函数时,操作的是设备文件;
而创建设备文件时,通过设备号把设备文件和内核层面的设备驱动联系起来,又通过file_operations操作方法结构体将函数逐个对应;
在内核层面,驱动通过操作寄存器,最后实现对硬件设备的控制
(三)用户空间和内核空间数据传输
1. 函数的参数对应关系
用户在应用层通过write函数将buff的内容传给内核层中mycdev_write函数中ubuf,
为了保证内核的安全性,需要通过copy_from_user函数,将ubuf的数据拷贝一份到kbuf中,通过操作kbuf来使用用户传入的数据。
read函数同理,用户在应用层通过read函数读取buf,其实在内核层面。是通过copy_to_user函数,将内核空间数据拷贝一份到kbuf,通过kbuf来传递给用户
使用*ubuf会报错:地址不允许访问
(四)字符设备相关的API
1. 字符设备驱动
(1)注册字符设备驱动
#include <linux/fs.h>
int register_chrdev(unsigned int major,const char *name,const struct file_operations * fops)
功能:注册字符设备驱动; (register char device)
参数:
@major:主设备号(申请到一个主设备号后,0-255的这256个次设备号会全部被分配)
major > 0 用户指定主设备号,一般不使用,因为如果与系统已定义的主设备号冲突就会报错
major = 0 系统动态分配主设备号
@name:设备的名字
@fops:操作方法结构体指针
struct file_operations{
int (*open) (struct inode *, struct file *);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*release) (struct inode *, struct file *);
}
返回值:
major > 0 ,成功返回0,失败返回错误码
major = 0 ,成功返回主设备号,失败返回错误码
- 补:
cat /proc/devices
查看设备名
(2)销毁字符设备驱动
#include <linux/fs.h>
int unregister_chrdev(unsigned int major,const char *name)
功能:销毁字符设备驱动
参数:
@major:主设备号
@name:设备的名字
返回值:
无
2. 用户和内核数据传输
(1)copy_to_user
#include <linux/uaccess.h>
int copy_to_user(void __user volatile *to, const void *from,unsigned long n)
功能:将数据从内核空间拷贝到用户空间(在驱动的在驱动的read中使用)
参数:
@to:用户空间的数据首地址
@from:内核空间数据首地址
@n:大小,单位是字节
返回值:
成功返回0,失败返回未拷贝的字节个数
(2)copy_from_user
int copy_from_user(void *to, const void __user volatile *from,unsigned long n)
功能:将数据从用户空间拷贝到内核空间(在驱动的在驱动的write中使用)
参数:
@to:内核空间的数据首地址
@from:用户空间数据首地址
@n:大小,单位是字节
返回值:
成功返回0,失败返回未拷贝的字节个数
- 注:两个函数都是第一个参数是拷贝到哪,第二个参数是从哪里拷贝
3. 物理地址映射虚拟地址
如果要将开发板上的LED点亮,那就必须操作LED灯对应的寄存器,寄存器的地址是物理地址。
驱动运行在3-4G的虚拟地址空间,所以没有办法直接控制LED的亮灭。如果想要在内核空间操作LED那就必须将LED灯的物理地址映射成虚拟地址,以后在内核空间操作这块虚拟地址就相当于在操作LED的物理地址。
(1)地址映射
#include <linux/io.h>
void *ioremap(unsigned long phy_addr, unsigned long size)
功能:将物理地址映射成虚拟地址
参数:
@phy_addr:物理地址
@size:映射的大小,单位是字节
返回值:
成功返回内核空间的虚拟地址,失败返回NULL
- 注:
- ioremap一次最多映射4k字节地址
(2)取消地址映射
void iounmap(void *virt_addr)
功能:取消地址映射
参数:
@virt_addr:虚拟地址
返回值:
无
- 注:地址映射后,如果不取消映射就会造成内存泄漏
二、驱动代码示例
(一)查看芯片手册
1. 寄存器基地址
图片1
(一)驱动代码
功能需求:实现LED1,LED2,LED3交替亮1s
需求分析:
LED1—PE10
LED2—PF10
LED3—PE8
GPIOE基地址—0x50006000
GPIOF基地址—0x50007000
RCC_AHB4基地址—0x50000A28
代码实现:
myled.h
#ifndef __MYLED_H__
#define __MYLED_H__
#define GPIOE_BASE 0x50006000
#define GPIOF_BASE 0x50007000
#define RCC 0x50000A28
typedef struct{
unsigned int gpiox_0:1;
unsigned int gpiox_1:1;
unsigned int gpiox_2:1;
unsigned int gpiox_3:1;
unsigned int gpiox_4:1;
unsigned int gpiox_5:1;
unsigned int gpiox_6:1;
unsigned int gpiox_7:1;
unsigned int gpiox_8:1;
unsigned int gpiox_9:1;
unsigned int gpiox_10:1;
unsigned int gpiox_11:1;
unsigned int gpiox_12:1;
unsigned int gpiox_13:1;
unsigned int gpiox_14:1;
unsigned int gpiox_15:1;
}bitf1_t;
typedef struct{
unsigned int gpiox_0:2;
unsigned int gpiox_1:2;
unsigned int gpiox_2:2;
unsigned int gpiox_3:2;
unsigned int gpiox_4:2;
unsigned int gpiox_5:2;
unsigned int gpiox_6:2;
unsigned int gpiox_7:2;
unsigned int gpiox_8:2;
unsigned int gpiox_9:2;
unsigned int gpiox_10:2;
unsigned int gpiox_11:2;
unsigned int gpiox_12:2;
unsigned int gpiox_13:2;
unsigned int gpiox_14:2;
unsigned int gpiox_15:2;
}bitf2_t;
typedef struct{
volatile bitf2_t MODER;
volatile bitf1_t OTYPER;
volatile bitf2_t OSPEEDR;
volatile bitf2_t PUPDR;
volatile bitf1_t IDR;
volatile bitf1_t ODR;
}gpio_t;
#define LED1 1
#define LED2 2
#define LED3 3
#endif
myled.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/io.h>
#include "myled.h"
#define CNAME "myled"
int major; //设备号
gpio_t *gpioe;
gpio_t *gpiof;
unsigned int *rcc;
int kbuf[2];
int mychrdev_open(struct inode *inode, struct file *file){
pr_info("%s:%d\n",__func__,__LINE__);
//将虚拟地址转换为物理地址
rcc=ioremap(RCC,4);
if(NULL == rcc){
pr_err("RCC ioremap error");
return -ENOMEM;
}
gpioe=ioremap(GPIOE_BASE,sizeof(gpio_t));
if(NULL == gpioe){
pr_err("GPIOE_BASE ioremap error");
return -ENOMEM;
}
gpiof=ioremap(GPIOF_BASE,sizeof(gpio_t));
if(NULL == gpiof){
pr_err("GPIOF_BASE ioremap error");
return -ENOMEM;
}
/***初始化LED灯***/
//使能时钟源
*rcc |= 0x3<<4;//RCC使能GPIOE和GPIOF
//PE10和PE8输出模式01
gpioe->MODER.gpiox_10=1;
gpioe->MODER.gpiox_8=1;
gpiof->MODER.gpiox_10=1;
//输出低电平
gpioe->ODR.gpiox_10=0;
gpioe->ODR.gpiox_8=0;
gpiof->ODR.gpiox_10=0;
return 0;
}
ssize_t mychrdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *offset){
int ret;
pr_info("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret) {
pr_err("copy_to_user error\n");
return -EIO;
}
return 0;
}
ssize_t mychrdev_write(struct file *file, const char __user *ubuf, size_t size, loff_t *offset){
int ret;
pr_info("%s:%s:%d\n", __FILE__, __func__, __LINE__);
memset(kbuf, 0, sizeof(kbuf));
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret) {
pr_err("copy_from_user error\n");
return -EIO;
}
switch(kbuf[0]){
case LED1:
kbuf[1]==1?(gpioe->ODR.gpiox_10=1):(gpioe->ODR.gpiox_10=0);
break;
case LED2:
kbuf[1]==1?(gpiof->ODR.gpiox_10=1):(gpiof->ODR.gpiox_10=0);
break;
case LED3:
kbuf[1]==1?(gpioe->ODR.gpiox_8=1):(gpioe->ODR.gpiox_8=0);
break;
}
return 0;
}
int mychrdev_close(struct inode *inode, struct file *file){
pr_info("%s:%d\n",__func__,__LINE__);
//取消虚拟映射
iounmap(rcc);
iounmap(gpioe);
iounmap(gpiof);
return 0;
}
const struct file_operations myfops = {
.open=mychrdev_open,
.read=mychrdev_read,
.write=mychrdev_write,
.release=mychrdev_close,
};
static int __init mychrdev_init(void){
//注册设备
major = register_chrdev(0,CNAME,&myfops);
if(major < 0){
pr_err("register error:%d\n",major);
return major;//返回错误码
}
pr_info("major = %d\n",major);//打印设备号
return 0;
}
static void __exit mychrdev_exit(void){
//注销设备
unregister_chrdev(major,CNAME);
}
module_init(mychrdev_init);
module_exit(mychrdev_exit);
MODULE_LICENSE("GPL");
- 注:使用的交叉编译器版本比较老,对语法检查要求更高,要求变量只能在函数开头定义,否则报错。
(二)测试
- 补:错误码
/include/uapi/asm0generic/errno-base.h