Linux系统驱动(二)字符设备驱动

一、概念

(一)相关概念

设备号:内核识别驱动的唯一的编号,设备号由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. 寄存器基地址

(一)驱动代码

功能需求:实现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
    在这里插入图片描述
  • 25
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值