概述
Linux驱动-字符设备驱动
- 前言
- 一、预备知识
- 1、file_operations结构体
- 2、地址映射
- 二、涉及的API函数
- 1、字符设备驱动
- 1.1、设备号
- 1.1.1、register_chrdev_region函数
- 1.1.2、alloc_chrdev_region函数
- 1.1.3、unregister_chrdev_region函数
- 1.2、字符设备
- 1.2.1、cdev_init函数
- 1.2.2、dev_add函数
- 1.2.3、cdev_del函数
- 1.3、类
- 1.3.1、class_create函数
- 1.3.2、class_destroy函数
- 1.4、设备
- 1.4.1、device_create函数
- 1.4.2、device_destroy函数
- 2、地址映射
- 2.1、ioremap函数
- 2.2、iounmap函数
- 3、I/O 内存访问
- 3.1、读函数
- 3.2、写操作
- 三、程序编写
- 1、驱动程序
- 2、应用程序
- 3、Makefile
前言
本文章:
- Linux系统下字符设备驱动的开发
- 介绍file_operations和地址映射
- 字符设备驱动开发过程中涉及的函数
- 展示了LED驱动的代码
一、预备知识
1、file_operations结构体
在Linux下,编写驱动程序实际上是实现对设备文件对应操作函数的编写,而这些操作函数是结构体file_operations中函数指针所指函数的具体实现。
因此,file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
2、地址映射
Linux作为一个庞大的操作系统,肯定是不允许用户直接通过物理地址对存储空间进行操作的,所以为了保证CPU执行指令时可正确访问存储单元,系统需要将物理地址和用户程序中的逻辑地址进行对应的转换 ,这一过程称为地址映射。
二、涉及的API函数
1、字符设备驱动
1.1、设备号
1.1.1、register_chrdev_region函数
int register_chrdev_region(dev_t from, unsigned count, const char *name)
作用:向内核申请设备号
注意:该函数需要我们指定要申请的设备号,不能与已分配的设备号冲突
1.1.2、alloc_chrdev_region函数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
作用:向内核动态申请设备号
优点:无需用户指定设备号,由系统为用户分配
1.1.3、unregister_chrdev_region函数
void unregister_chrdev_region(dev_t from, unsigned count)
作用:释放已经分配出去的设备号
1.2、字符设备
1.2.1、cdev_init函数
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
作用:对cdev变量进行初始化
1.2.2、dev_add函数
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
作用:于向 Linux 系统添加字符设备
1.2.3、cdev_del函数
void cdev_del(struct cdev *p)
作用:从 Linux 内核中删除相应的字符设备
1.3、类
1.3.1、class_create函数
struct class *class_create (struct module *owner, const char *name)
作用:创建class类
1.3.2、class_destroy函数
void class_destroy(struct class *cls)
作用:删除掉创建的类
1.4、设备
1.4.1、device_create函数
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
作用:创建设备
1.4.2、device_destroy函数
void device_destroy(struct class *class, dev_t devt)
作用:卸载创建的设备
2、地址映射
2.1、ioremap函数
void __iomem *ioremap(phys_addr_t offset, size_t size)
作用:将物理地址映射为虚拟地址
2.2、iounmap函数
void iounmap(void __iomem *addr)
作用:释放掉 ioremap 函数所做的映射
3、I/O 内存访问
3.1、读函数
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
作用:读取地址为addr的存储内容
区别:8bit、16bit和32bit的读操作
3.2、写操作
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
作用:向地址为addr的存储空间写入数据
区别:8bit、16bit和32bit的写操作
三、程序编写
1、驱动程序
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/io.h>
#include <asm/uaccess.h>
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虚拟地址指针 */
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* 自己抽象出来的设备结构体 */
struct led_cdev_type
{
dev_t id;
struct cdev cdev;
struct class *class;
struct device *device;
};
struct led_cdev_type led_cdev;
/**
* @brief 文件操作结构体-write函数
*
*/
ssize_t led_write(struct file *file, const char __user *buf, size_t len, loff_t *loff_t)
{
unsigned char data[1];
uint32_t temp;
unsigned long err;
/* 获取应用层数据 */
err = copy_from_user(data, buf, 1);
/* 根据传入数据设置LED的开关状态 */
if(data[0] == '1')
{
temp = readl(GPIO1_DR);
temp &= ~(1 << 3);
writel(temp, GPIO1_DR);
}
else
{
temp = readl(GPIO1_DR);
temp |= (1 << 3);
writel(temp, GPIO1_DR);
}
return 0;
}
/**
* @brief 文件操作结构体
*
*/
static struct file_operations led_ops = {
.owner = THIS_MODULE,
.write = led_write,
};
/**
* @brief 配置LED的引脚
*
*/
static void led_IO_config(void)
{
uint32_t temp;
/* 1、地址映射 */
CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2、使能时钟 */
temp = readl(CCM_CCGR1);
temp &= ~(3 << 26);
temp |= (3 << 26);
writel(temp, CCM_CCGR1);
/* 3、IO复用和配置 */
writel(5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、设置GPIO方向 */
temp = readl(GPIO1_GDIR);
temp |= (1 << 3);
writel(temp, GPIO1_GDIR);
/* 5、默认状态:关闭 */
temp = readl(GPIO1_DR);
temp |= (1 << 3);
writel(temp, GPIO1_DR);
}
/**
* @brief 取消地址映射
*
*/
static void led_IO_unmap(void)
{
iounmap(CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
}
/**
* @brief 入口函数
*
*/
static int __init led_init(void)
{
/* 1、申请设备号 */
alloc_chrdev_region(&led_cdev.id, 0, 1, "my_led");
/* 2、初始化字符设备并向内核添加字符设备 */
cdev_init(&led_cdev.cdev, &led_ops);
cdev_add(&led_cdev.cdev, led_cdev.id, 1);
/* 3、创建类 */
led_cdev.class = class_create(THIS_MODULE, "my_led");
/* 4、创建设备 */
led_cdev.device = device_create(led_cdev.class, NULL, led_cdev.id, NULL, "my_led");
/* 5、LED IO配置 */
led_IO_config();
return 0;
}
/**
* @brief 出口函数
*
*/
static void __exit led_exit(void)
{
/*5、 取消地址映射 */
led_IO_unmap();
/* 4、删除设备 */
device_destroy(led_cdev.class, led_cdev.id);
/* 3、删除类 */
class_destroy(led_cdev.class);
/* 2、删除字符设备 */
cdev_del(&led_cdev.cdev);
/* 1、释放设备号 */
unregister_chrdev_region(led_cdev.id, 1);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LSW");
2、应用程序
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char **argv)
{
int fd;
int err;
if(argc != 3)
{
printf("Error: Invalid number of argumentsn");
return -1;
}
/* 打开文件 */
fd = open(argv[1], O_RDWR);
/* 写文件 */
write(fd, argv[2], sizeof(char));
/* 关闭文件 */
close(fd);
return 0;
}
3、Makefile
export ARCH=arm
export CROSS_COMPILE=(对应的编译器)
KERNEL_DIR := (Linux内核路径)
CURRENT_DIR := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
最后
以上就是复杂早晨为你收集整理的Linux驱动-字符设备驱动前言一、预备知识二、涉及的API函数三、程序编写的全部内容,希望文章能够帮你解决Linux驱动-字符设备驱动前言一、预备知识二、涉及的API函数三、程序编写所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复