新字符设备驱动

warren
2024-07-30 / 0 评论 / 4 阅读 / 正在检测是否收录...

设备号的分配和获取

在之前的实验中,我们使用register_chrdev函数来向内核注册一个字符设备,使用unregister_chrdev释放注册的设备号。他们的使用参数都需要我们指定一个设备号。但在使用过程中这样的操作存在两个问题。

  1. 我们如何事先知道那些设备号没有被使用?
  2. register_chrdev只需要传入主设备号,次设备号没有被使用,造成浪费?
    为此采用新的函数来处理设备号的使用,对申请而言,我们分为两种情况。第一种是我们不指定设备号,第二种是我们指定设备号。
    如果没有指定设备号的话就使用如下函数来申请设备号

    //dev_t *dev 存放设备号的地址
    //unsigned baseminor 起始次设备号
    //unsigned count 要申请的设备号数量(一次申请多个,主设备号一样,次设备号不同)
    //const char *name 设备名
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

    如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可

    //dev_t from 要申请的起始设备号
    //unsigned count 要申请的设备号数量
    //const char *name 设备名
    int register_chrdev_region(dev_t from, unsigned count, const char *name)

    释 放 掉 设 备 号 , 不 管 是 通 过 alloc_chrdev_region 函 数 还 是
    register_chrdev_region 函数申请的设备号,统一使用如下释放函数

    //dev_t from 要释放的设备号
    //unsigned count 表示从 from 开始,要释放的设备号数量
    void unregister_chrdev_region(dev_t from, unsigned count)

    dev_t 设备号

    dev_t结构体实际上就是一个uint32_t的变量。其中高12bit代表主设备号,低20bit代表次设备号。
    在文件include/linux/kdev_t.h中关于设备号的操作函数如下所示:

    #define MINORBITS XX
    #define MINORMASK ((1U << MINORBITS) - 1)
    #define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

    字符设备结构

    在 Linux 中使用 cdev 结构体表示一个字符设备, cdev 结构体在 include/linux/cdev.h 文件中

    //kobj:
    //*owner:填写THIS_MODULE
    //*ops:文件操作函数集合
    //list:
    //dev:设备号
    //count:
      struct cdev {
       struct kobject kobj;
       struct module *owner;
       const struct file_operations *ops;
       struct list_head list;
       dev_t dev;
       unsigned int count;
      }

    定义好cdev变量后通过cdev_init 函数对其进行初始化。

    //*cdev:字符设备变量地址
    //*fops:字符设备对应操作函数集合地址
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)

    初始化完cdev变量需要调用cdev_add函数向Linux系统添加字符设备

    //*p: cdev结构体变量地址
    //dev: 设备所使用的设备号
    //count: 要添加的设备数量
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)

    卸载驱动时候要使用cdev_del函数,这样才能从Linux内核中删除对应的字符设备

    //*p: cdev结构体变量地址
    void cdev_del(struct cdev *p)

    自动创建删除设备节点

    为了实现自动创建设备节点及自动删除设备节点。需要引入mdev机制,mdev 来实现设备节点文件的自动创建与删除,热插拔事件也由 mdev 管理。
    自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码( 类的创建和设备的创建 )

    //*owner 填写THIS_MODULE
    //char *name 类名称
    //return 创建好的类
    struct class *class_create (struct module *owner, const char *name)
    //*class 创建的类
    //*parent 一般为NULL
    //devt 设备号
    //drvdata 一般为NULL
    //*fmt  设备名
    //return 创建好的设备
    struct device *device_create(struct class *class,struct device *parent,dev_t devt,void *drvdata,const char *fmt, ...)

    同样的在卸载驱动时候需要删除掉创建的类和设备

    //*cls 要删除的类的地址
    void class_destroy(struct class *cls);
    //*class 要删除的设备所处的类
    //devt 要删除的设备号
    void device_destroy(struct class *class, dev_t devt)

    设置文件私有数据

    就是将这一系列的变量封成一个结构体,然后再open函数中设置filp的私有数据指向该结构体变量。
    设置好私有数据以后,在 write、 read、 close 等函数中直接读取 private_data 即可得到设备结构体
    例子:

    struct test_dev{
      dev_t devid; /* 设备号 */
      struct cdev cdev; /* cdev */
      struct class *class; /* 类 */
      struct device *device; /* 设备 */
      int major; /* 主设备号 */
      int minor; /* 次设备号 */
    };
    
    static int test_open(struct inode *inode, struct file *filp)
    {
      filp->private_data = &testdev; /* 设置私有数据 */
      return 0;
    }

    驱动代码实现

    #include <linux/types.h>
    #include <linux/kernel.h>
    #include <linux/delay.h>
    #include <linux/ide.h>
    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/errno.h>
    #include <linux/gpio.h>
    #include <linux/cdev.h>
    #include <linux/device.h>
    #include <asm/mach/map.h>
    #include <asm/uaccess.h>
    #include <asm/io.h>
    
    
    #define NEWCHRLED_CNT   1
    #define NEWCHRLED_NAME "newchrled"
    
    #define LEDOFF 0
    #define LEDON  1
    
    #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 newchrled_dev{
     dev_t devid;
     struct cdev cdev;
     struct class *class;
     struct device *device;
     int major;
     int minor;
    };
    
    
    struct newchrled_dev newchrled;
    
    void led_switch(u8 sta)
    {
     u32 val = 0;
     if(sta == LEDOFF){
         val = readl(GPIO1_DR);
         val |= (1 << 3);
         writel(val, GPIO1_DR);
     }else if(sta == LEDON){
         val = readl(GPIO1_DR);
         val &= ~(1 << 3);
         writel(val, GPIO1_DR);
     }
    }
    
    void led_device_init(void)
    {
     u32 val = 0;
    
     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);
    
     /*使能GPIO1时钟*/
     val = readl(CCM_CCGR1);
     val &= ~(3 << 26);
     val |= (3 << 26);
     writel(val, CCM_CCGR1);
    
     /*设置GPIO1——IO3的复用功能*/
     writel(5, SW_MUX_GPIO1_IO03);
    
     /*设置SW_PAD_GPIO1_IO03的IO属性*/
     writel(0x10B0, SW_PAD_GPIO1_IO03);
    
     /*设置GPIO1_IO03为输出功能*/
     val = readl(GPIO1_GDIR);
     val &= ~(1 << 3);
     val |= (1 << 3);
     writel(val, GPIO1_GDIR);
    
     /*关闭LED*/
     led_switch(LEDOFF);
    }
    
    u32 led_status_get(void)
    {
     u32 val = 0;
    
     val = readl(GPIO1_DR);
     val = (val >> 3)&0x01;
     return (!val);
    }
    
    void led_device_deinit(void)
    {
     led_switch(LEDOFF);
    
     iounmap(CCM_CCGR1);
     iounmap(SW_MUX_GPIO1_IO03);
     iounmap(SW_PAD_GPIO1_IO03);
     iounmap(GPIO1_DR);
     iounmap(GPIO1_GDIR);
    }
    
    
    static int led_open(struct inode *inode, struct file *filp)
    {
     filp->private_data = &newchrled;
     return 0;
    };
    
    static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
    {
     u32 led_status = 0;
    
     led_status = led_status_get();
     copy_to_user(buf, &led_status, sizeof(u32));
     return 0;
    }
    
    static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    {
     u32 led_ctrl = 0;
    
     copy_from_user(&led_ctrl, buf, sizeof(u32));
     led_switch(led_ctrl & 0x01);
     return 0;
    }
    
    static int led_release(struct inode *inode, struct file *filp)
    {
     return 0;
    }
    
    static struct file_operations newchrled_fops = {
     .owner = THIS_MODULE,
     .open = led_open,
     .read = led_read,
     .write = led_write,
     .release = led_release,
    };
    
    static int __init led_init(void)
    {
     led_device_init();
     if(newchrled.major){
         newchrled.devid = MKDEV(newchrled.major, 0);
         register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
     }else{
         alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
         newchrled.major = MAJOR(newchrled.devid);
         newchrled.minor = MINOR(newchrled.devid);
     }
     printk("newchrled major=%d minor=%d\n", newchrled.major, newchrled.minor);
    
     /*初始化cdev*/
     newchrled.cdev.owner = THIS_MODULE;
     cdev_init(&newchrled.cdev, &newchrled_fops);
    
     /*添加一个cdev*/
     cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
    
     /*创建类*/
     newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
     if(IS_ERR(newchrled.class)){
         return PTR_ERR(newchrled.class);
     }
    
     /*创建设备*/
     newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
     if(IS_ERR(newchrled.device)){
         return PTR_ERR(newchrled.device);
     }
     return 0;
    }
    
    static void __exit led_exit(void)
    {
     led_device_deinit();
    
     cdev_del(&newchrled.cdev);
     unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
    
     device_destroy(newchrled.class, newchrled.devid);
     class_destroy(newchrled.class);
    }
    
    module_init(led_init);
    module_exit(led_exit);
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("warren");

    测试代码实现

    #include "stdio.h"
    #include "unistd.h"
    #include "sys/types.h"
    #include "fcntl.h"
    #include "stdlib.h"
    #include "string.h"
    
    
    
    int main(int argc, int argv[])
    {   
     int retvalue = 0;
     int Dir = 0;
     char *filename;
     int fd = 0;
     unsigned int  buff;
    
    
     if(argc < 4){
         printf("Error Usage!\r\n");
         return -1;
     }
     filename = argv[1];
     fd = open(filename, O_RDWR);
     if(fd < 0){
         printf("Open %s error!\r\n",filename);
         return -1;
     }
     Dir = atoi(argv[2]);
     if(Dir == 1){//写入
         buff = atoi(argv[3]);
         retvalue = write(fd, &buff, sizeof(unsigned int));
     }else if(Dir == 2){//读取
         retvalue = read(fd, &buff, sizeof(unsigned int));
         printf("Led status is %d\r\n", buff);
     }
     return 0;
    }

    脚本文件

    编译脚本

    #!/bin/bash
    make clean
    make
    rm -rf ledApp
    arm-linux-gnueabihf-gcc ledApp.c -o ledApp
    rm -rf ../../tftpboot/*
    cp *App *.ko ../../tftpboot
    chmod 777 ../../tftpboot/*

    tftp获取脚本

    #!/bin/bash
    rm -rf led*
    tftp -g -r newchrled.ko 192.168.0.100
    tftp -g -r ledApp 192.168.0.100
    chmod 777 ledApp

    测试及实验

    1.关闭开发板的心跳灯

    echo none>/sys/class/leds/sys-led/trigger

    2.获取驱动模块文件和测试应用程序文件

    ./get.sh

    lzeysh7m.png
    3.加载newchrled.ko驱动模块

    depmod
    modprobe newchrled

    在实验depmod命令时,会报错。但是不影响正常使用,下个帖子将怎么解决
    lzeyruei.png
    lzeytof6.png
    可以看到申请到的主设备号为249,次设备号为0
    4.查看设备节点

    ls /dev/newchrled -l

    lzeyv9i5.png
    5.使用测试应用来测试

    ./ledApp /dev/newchrled 1 1
    ./ledApp /dev/newchrled 2 1
    ./ledApp /dev/newchrled 1 0
    ./ledApp /dev/newchrled 2 0

    lzez1wfk.png
    6.卸载驱动模块

    rmmod newchrled

    lzez5bbr.png

0

评论 (0)

取消