Linux驱动开发-字符设备驱动开发LED设备

发布于:2025-03-11 ⋅ 阅读:(83) ⋅ 点赞:(0)

一,LED设备驱动开发

1. Led驱动具体实现

1.1 驱动程序

#include <linux/module.h>
#include<linux/kernel.h>
#include<linux/init.h>
#include<linux/fs.h>
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.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;
/*设备号和ID*/
#define LED_MAJOR 200
#define LED_NAME  "leddev" 
static void led_choice(unsigned char in)
{
    static int register_led = 0;
    if(in==0)//turn on led
    {
        /*控制亮*/
        register_led = readl(GPIO1_DR); 
        register_led &=~(1<<3);
        writel(register_led,GPIO1_DR); 
    }else if(in==1)
    {
        register_led = readl(GPIO1_DR); 
        register_led |=(1<<3);
        writel(register_led,GPIO1_DR);
    }
}
static int led_open(struct inode *inode,struct file *filp)
{
    printk("open in linux\r\n");
    return 0;
}
static ssize_t led_read(struct file *filp, char __user *buf,size_t count,loff_t *ppos)
{
    return 0;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
    unsigned char writebuf[1];
    int ret = 0;

    ret = copy_from_user(writebuf, buf,count);
    if(ret!=0)
    {
        printk("write in linux error\r\n");
    }

    led_choice(writebuf[0]);

    return 0;
}
static int led_release(struct inode *inode,struct file *filp)
{
    return 0;
}
static const struct file_operations led_fops={
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release = led_release,
};

static int __init led_init(void) //加载函数
{
    int ret = 0;
    int register_result = 0;

    /*1.将物理地址*_BASE和虚拟地址联系起来*/
    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.初始化*/

    /*2.1 时钟初始化*/
    register_result = readl(CCM_CCGR1);
    register_result |=(3<<26);
    writel(register_result,CCM_CCGR1);

    /*2.2复用初始化*/
    writel(5,SW_MUX_GPIO1_IO03);

    /*2.3电器属性初始化*/
    writel(0x10b0,SW_PAD_GPIO1_IO03);

    /*2.4设置为输出模式*/
    register_result = readl(GPIO1_GDIR);   
    register_result |= (1<<3);
    writel(register_result,GPIO1_GDIR);

    /*2.5控制亮*/
    register_result = readl(GPIO1_DR); 
    register_result &=~(1<<3);
    writel(register_result,GPIO1_DR);    

    /*注册字符设备*/
    ret = register_chrdev(LED_MAJOR, LED_NAME,&led_fops);
    if(ret==0)
    {
         printk("init in linux\r\n");
    }
    return 0;
}
static void __exit led_exit(void )//卸载函数
{
    int register_result = 0;
    /*控制灭*/
    register_result = readl(GPIO1_DR); 
    register_result |=(1<<3);
    writel(register_result,GPIO1_DR); 

    /*1.取消虚拟地址映射*/
    iounmap(CCM_CCGR1);
    iounmap(SW_MUX_GPIO1_IO03);
    iounmap(SW_PAD_GPIO1_IO03);
    iounmap(GPIO1_DR);
    iounmap(GPIO1_GDIR);

    printk("exit in linux\r\n");

    /*注销*/
    unregister_chrdev(LED_MAJOR, LED_NAME);
}
/*加载和卸载函数*/
module_init(led_init);
module_exit(led_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("wyt");

1.2 应用

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(unsigned char argc,unsigned char *argv[])
{
    int rel = 0,fd = 0;
    unsigned char *filename,write_buff[1];
    filename = argv[1];

    fd = open(filename,O_RDWR);
    if(fd<0) printf("open file error\r\n");
    write_buff[0] = atoi(argv[2]);
    rel = write(fd, write_buff, 1);
    if(rel<0) printf("write in APP error\r\n");
    rel = close(fd);
    if(rel<0) printf("close in  APP error\r\n");
    return 0;

}

2.相关知识

2.1 内存地址映射

  内存地址映射 是指将物理内存、设备内存或文件内容映射到进程的虚拟地址空间,使得进程可以通过访问虚拟地址来操作这些资源。作用:文件映射:将文件内容映射到进程的虚拟地址空间,方便读写文件。设备内存映射:将设备的内存映射到进程的虚拟地址空间,方便访问设备寄存器。共享内存:多个进程(使用虚拟地址)可以通过页表共享同一块物理内存,用于进程间通信。(虚拟地址 和 物理地址 并不是一对一的关系,而是通过 页表 进行动态映射的)

  Linux中有MMU(memory manage unit)内存管理单元,主要实现:①虚拟空间到物理空间的映射,②内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。在裸机实验中,能够直接通过控制物理地址实现对寄存器的控制,设置引脚时钟,电器属性,引脚输入还是输出以及复用功能,但是在Linux中,要控制的是虚拟地址,通过虚拟地址来孔子物理地址。
在这里插入图片描述

2.2 映射函数

  ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 。iounmap 函数释放掉 ioremap 函数所做的映射。
在这里插入图片描述
在这里插入图片描述

2.3 Linux内核中读写操作函数

  读写操作函数实现的就是从虚拟地址中读写数据,即读或写得到虚拟地址对应物理地址中的数据,(裸机实验中,得到寄存器的物理地址后,用指针访问这些地址对应的数据)。用法如下:
在这里插入图片描述在这里插入图片描述

二,新字符设备驱动开发

1. 注册函数以及自动设置设备号(API函数)

1.1注册

int register_chrdev_region(dev_t first, unsigned int count, const char *name);
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);

  register_chrdev_region功能手动注册一个设备号范围,first:注册设备号,count:注册设备号数量,name:设备名称。alloc_chrdev_region功能动态分配一个设备号范围,dev:输出参数,即分配出的设备号,firstminor:起始次设备号(通常为0),count:要分配的设备号数量,name:设备名称。

void cdev_init(struct cdev *cdev, const struct file_operations *fops);
int cdev_add(struct cdev *cdev, dev_t dev, unsigned int count);

  cdev_init:初始化一个 struct cdev 结构体,将其与文件操作函数(file_operations)关联,cdev:表示初始化的字符设备,fops:字符设备的文件操作函数集合。cdev_add:将初始化后的字符设备添加到内核中,使其对用户空间可见,可以看出第一个初始化init函数将文件操作函数和字符设备关联后,add添加函数就这个字符设备,设备号,数量添加到内核中。

1.2 注销

  注销时候使用两个函数,要有先后顺序,先cdev_del(删除字符设备,使其从内核的设备列表移出) ,再unregister_chrdev_region(释放设备号,使其从内核设备号池中移除),如果先释放设备号,可能出现内核在删除字符设备时无法正确找到字符设备,导致cdev_del失败或者内核崩溃,或者不使用cdev_del,会导致字符设备仍然在内核的设备列表中。

整体实现:

#define NEWLED_NAME     "newled"
struct led_device {
    struct class *class;  //类
    struct device *device;  //设备节点
    struct cdev  newled_cdev; //字符设备
    int major;  //主设备号
    int minor; //次设备号
    dev_t devid; //设备号
};
static const struct file_operations newled_fops={
    .owner = THIS_MODULE,
    .open = newled_open,
    .read = newled_read,
    .write = newled_write,
    .release = newled_release,   
};
    /*一,注册字符设备*/
    /*1.1.设备号*/
    if(newled.major)
    {
        newled.devid = MKDEV(newled.major,0);
        ret = register_chrdev_region(newled.devid,1,"NEWLED_NAME");
    }else
    {
        ret = alloc_chrdev_region(&newled.devid, 0, 1, "NEWLED_NAME");
        newled.major = MAJOR(newled.devid);
        newled.minor = MINOR(newled.devid);
    }   unregister_chrdev_region(newled.devid, 1);//注销与设备号相关
    printk("major = %d,minor = %d \r\n",newled.major,newled.minor);
    
    /*1.2设备注册函数*/
    newled.newled_cdev.owner = THIS_MODULE;
    cdev_init(&newled.newled_cdev,&newled_fops);
    cdev_add(&newled.newled_cdev,newled.devid,1);

	/*注销字符设备*/
    cdev_del(&newled.newled_cdev);//删除dev,即和注册函数相关
    unregister_chrdev_region(newled.devid, 1);//注销与设备号相关

2.自动创建设备节点

2.1 创建节点

  这部分作用就是在/dev/自动创建设备节点,先创建一个class类(设备类是对具有相似功能的设备的一种逻辑分组,例如所有的 LED 设备可以归为一个 LED 类。):owner一般为THIS_MODULE,参数name是类名字,然后在类下面创建设备,device_create可变参数函数,作用是在/dev/下创建一个设备文件,使得用户空间程序可以通过该文件和内核模块中的字符设备进行交互,相当于是一个桥梁。

	struct class *class_create(struct module *owner,const char *name);
	struct device *device_create(
    struct class *class,      // 设备所属的类
    struct device *parent,    // 父设备(通常为 NULL)
    dev_t devt,               // 设备号
    void *drvdata,            // 驱动数据(通常为 NULL)
    const char *fmt, ...      // 设备名称(格式化字符串)
);

2.2 销毁节点

	void device_destroy(struct class *class,dev_t devt);
	void class_destroy(struct class *cls);

   顺序是,首先摧毁设备节点,其次摧毁类,如果先摧毁类,设备在销毁过程中可能会尝试访问已经被销毁的类的相关信息,从而导致内核崩溃或者产生错误信息。由于类已经被销毁,设备可能无法正确释放其占用的资源,从而导致资源泄漏。

整体函数实现:

    /*2.1整个设备类的创建*/
    newled.class = class_create(THIS_MODULE,NEWLED_NAME);//创建类
    if(IS_ERR(newled.class))
    {
        return PTR_ERR(newled.class);
    }

    /*2.2具体实现让这个设备放到类中*/
    newled.device = device_create(newled.class,NULL,newled.devid,NULL,NEWLED_NAME);
    if(IS_ERR(newled.device))
    {
        return PTR_ERR(newled.device);
    }

	/*摧毁设备和类*/
	device_destroy(newled.class,newled.devid);//先摧毁设备
    class_destroy(newled.class);//摧毁类


网站公告

今日签到

点亮在社区的每一天
去签到