Linux驱动-字符设备驱动

前言

本文章:

  1. Linux系统下字符设备驱动的开发
  2. 介绍file_operations和地址映射
  3. 字符设备驱动开发过程中涉及的函数
  4. 展示了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 arguments\n");
        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
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Vis-Lin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值