WDS1期第12课 字符设备驱动 3 led4个独立驱动 mdev根据代码自动创建次设备节点 获取次设备号 用户代码useage


1. 写驱动框架
2. 硬件相关(原理图 手册 写代码)

一、board相关原理图和芯片datasheet

1. 原理图

在这里插入图片描述在这里插入图片描述

2. datasheet

在这里插入图片描述在这里插入图片描述

二、驱动/用户/Makefile代码

1. led驱动led_chrdev1.c

#include <linux/init.h>     // module_init module_exit
#include <linux/module.h>   // MODULE_LICENSE
#include <linux/fs.h>       // file_operations
#include <linux/cdev.h>     // cdev
#include <linux/kernel.h>
#include <linux/device.h>   // class_device
#include <asm/uaccess.h>    // copy_from_user copy_to_user
#include <asm/io.h>         // ioremap  iounmap

// GPF引脚的con和dat寄存器,地址需要映射
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;

// 用于自动创建设备节点的结构class 和 class_device
static struct class *demo_class;
static struct class_device *demo_class_devs;

// 操作方法
//  配置GPF4/5/6为输出模式
static int led_open(struct inode *inode, struct file *filep)
{
    *gpfcon &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));   // 先清零 8-13 01010101
    *gpfcon |=   (0x1<<(4*2)  |  0x1<<(5*2)  |  0x1<<(6*2));    // 再对应位置1
    printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
//  配置GPF4/5/6输出高或者低电平
static ssize_t led_write(struct file *filep, const char __user *buf, size_t count, loff_t *ppos)
{
    // 在用户层传入的形式是 write(fd, &val, 4); val是buf,4是count
    // 在内核层获取用户层传入的参数,用copy_from_user; 内核空间到用户传参数copy_to_user
    int user_param = 0;
    copy_from_user(&user_param, buf, count); // 从buf拷贝到user_param,长度为count
    if (1 == user_param)        // 开灯
    {
        // 4-6 000
        *gpfdat &= ~((0x7<<4));     
    }
    else if (0 == user_param)   // 关灯
    {
        *gpfdat |= (0x7<<4);
    }
    printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);

    return 0;
}
// 操作方法集
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open  = led_open,
    .write = led_write,
};

// 用于保存获取到的主设备号
unsigned int major = 0; 

// 入口函数
static int demo_cdev_init(void)
{
    // 自动获取主设备号资源;主设备号对应的名字在/proc/devices中,
    //  内核驱动模块的名字是Makefile里指定的(一般跟驱动.c文件名字对应)
    major = register_chrdev(0, "major_name_led", &fops); //  0自动分配, 
    
    // 以下为 在系统中生成设备信息的步骤
    // 1. 新建一个class
    demo_class = class_create(THIS_MODULE, "led_chrdev_class1");
    if (IS_ERR(demo_class))
        return PTR_ERR(demo_class);
    // 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx
    //  在/dev目录下创建相应的设备节点
    demo_class_devs = class_device_create(demo_class, NULL, MKDEV(major, 0), NULL, "led_on_off1"); 
    if (unlikely(IS_ERR(demo_class_devs)))
        return PTR_ERR(demo_class_devs);

    // led寄存器的地址映射,只需要执行一次,所以在入口函数映射
    gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16); // start, size
    gpfdat = gpfcon + 1;                                       // unsigned long的长度

    return 0;
}
// 出口函数
static void demo_cdev_exit(void)
{
    // 对应卸载
    unregister_chrdev(major, "major_name_led");
    class_device_unregister(demo_class_devs);  
    class_destroy(demo_class);

    // 去掉映射关系
    iounmap(gpfcon);
}

// 内核模块相关
module_init(demo_cdev_init);
module_exit(demo_cdev_exit);
MODULE_LICENSE("GPL");

2. Makefile

KERN_DIR := /home/wuyexkx/Desktop/韦东山/system/linux-2.6.22.6

PWD := $(shell pwd)		# 执行pwd命令并把结果赋给PWD

obj-m := led_chrdev1.o  # .ko的生成依赖于.o,.o默认依赖.c

all:
	make -C $(KERN_DIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm # modules为编译目标
clean:
	make -C $(KERN_DIR) M=$(PWD) clean

3. 用户user_led1.c

#include <fcntl.h>
#include <stdio.h>

// main传入2个参数 user_led1 on/off
int main(int argc, char **argv)
{
    int fd;
    int val = 1;
    // 打开 mdev根据驱动程序中的class_device_create自动创建的 设备节点
    fd = open("/dev/led_on_off1", O_RDWR);
    if(fd < 0)
        printf("can't open '/dev/led_on_off1'\n");
    
    // main参数个数判断
    if(argc != 2) 
    {
        printf("Usage:\n");
        printf("\t%s <on|off>\n", argv[0]);
        return 0;
    }
    // 第二参数判断
    if(0 == strcmp(argv[1], "on"))      // 开灯参数
    {
        val = 1;
    }
    else if(0 == strcmp(argv[1], "off"))// 关灯参数
    {
        val  = 0;
    }
    // 根据第二参数write
    write(fd, &val, 4);

    return 0;
}

三、插入内核模块 自动创建设备节点 执行用户led

查看插入的内核驱动模块,cat proc/devices
在这里插入图片描述
查看自动创建的设备节点,ls dev/led_on_off1 -l
在这里插入图片描述
执行应用程序,./mnt/tzb/user_led1提示使用信息;
./mnt/tzb/user_led1 on开灯;
./mnt/tzb/user_led1 off关灯:
在这里插入图片描述

四、用次设备号改进led驱动代码

1. led驱动led_chrdev2.c

#include <linux/init.h>     // module_init module_exit
#include <linux/module.h>   // MODULE_LICENSE
#include <linux/fs.h>       // file_operations
#include <linux/cdev.h>     // cdev
#include <linux/kernel.h>
#include <linux/device.h>   // class_device
#include <asm/uaccess.h>    // copy_from_user copy_to_user
#include <asm/io.h>         // ioremap  iounmap

#define DEVICE_NAME     "leds" // 加载模式后,执行”cat /proc/devices”命令看到的设备名称
int major = 0;                 // 用于保存获取到的主设备号

// GPF引脚的con和dat寄存器,地址需要映射
volatile unsigned long *gpfcon = NULL;
volatile unsigned long *gpfdat = NULL;
#define GPFCON      (*gpfcon)
#define GPFDAT      (*gpfdat)

// 用于自动创建设备节点的结构class 和 class_device
static struct class *leds_class;
static struct class_device *leds_class_dev[4];

// 操作方法
//  根据次设备号 配置GPF4/5/6为输出模式
static int leds_open(struct inode *inode, struct file *filep)
{
    // 从inode中提取次设备号
	int minor = MINOR(inode->i_rdev); 
    
    switch(minor)
    {
        case 0: // leds
        {
            GPFCON &= ~((0x3<<(4*2)) | (0x3<<(5*2)) | (0x3<<(6*2)));   // 先清零 8-13 01010101
            GPFCON |=   (0x1<<(4*2)  |  0x1<<(5*2)  |  0x1<<(6*2));    // 再对应位置1
            break;
        }
        case 1: // led1
        {
            GPFCON &= ~(0x3<<(4*2));
            GPFCON |=   (0x1<<(4*2));
            break;
        }
        case 2: // led2
        {
            GPFCON &= ~(0x3<<(5*2));
            GPFCON |=   (0x1<<(5*2));
            break;
        }
        case 3: // led3
        {
            GPFCON &= ~(0x3<<(6*2));
            GPFCON |=   (0x1<<(6*2));
            break;
        }
    }
    printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);

    return 0;
}
//  配置GPF4/5/6输出高或者低电平
static ssize_t leds_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
    // 获取次设备号
	int minor = MINOR(filp->f_dentry->d_inode->i_rdev);

    // 在用户层传入的形式是 write(fd, &val, 4); val是buf,4是count
    // 在内核层获取用户层传入的参数,用copy_from_user; 内核空间到用户传参数copy_to_user
    int user_param = 0;
    copy_from_user(&user_param, buf, count); // 从buf拷贝到user_param,长度为count
    
    switch(minor)
    {
        case 0:
        { 
            if (user_param == 1)
                GPFDAT |= (0x7 << 4);  
            else if (user_param == 0)
                GPFDAT &= ~(0x7 << 4);    // 4-6 000
            break;
        }
        case 1:
        { 
            if (user_param == 1)
                GPFDAT |= (0x1 << 4);  
            else if (user_param == 0)
                GPFDAT &= ~(0x1 << 4);    // 4 0
            break;
        }
        case 2:
        { 
            if (user_param == 1)
                GPFDAT |= (0x1 << 5);  
            else if (user_param == 0)
                GPFDAT &= ~(0x1 << 5);    // 5 0
            break;
        }
        case 3:
        { 
            if (user_param == 1)
                GPFDAT |= (0x1 << 6);  
            else if (user_param == 0)
                GPFDAT &= ~(0x1 << 6);    // 6 0
            break;
        }
    }
    printk("--%s--%s--%d\n", __FILE__, __func__, __LINE__);

    return 0;
}
// 操作方法集
static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open  = leds_open,
    .write = leds_write,
};

// 入口函数
static int leds_init(void)
{
    int minor = 0; // 保存次设备号

    // led寄存器的地址映射,只需要执行一次,所以在入口函数映射
    gpfcon = (volatile unsigned long*)ioremap(0x56000050, 16); // start, size
    gpfdat = gpfcon + 1;                                       // unsigned long的长度

    // 0. 自动获取主设备号资源;主设备号对应的名字在/proc/devices中,
    //      内核驱动模块的名字是Makefile里指定的(一般跟驱动.c文件名字对应)
    major = register_chrdev(0, DEVICE_NAME, &fops); //  0自动分配, 
    if (major < 0)
    {
        printk(DEVICE_NAME " can't register major number.\n");
        return major;
    }

    // 以下为 在系统中生成设备信息的步骤
    // 1. 新建一个class
    leds_class = class_create(THIS_MODULE, "leds_class");
    if (IS_ERR(leds_class))
        return PTR_ERR(leds_class);
    // 2. 在class里边创建一个设备叫xxx,然后mdev自动创建设备节点/dev/xxx
    //  在/dev目录下创建相应的设备节点,0全部led设备节点
    leds_class_dev[0] = class_device_create(leds_class, NULL, MKDEV(major, 0), NULL, "led0"); 
    if (unlikely(IS_ERR(leds_class_dev[0])))
        return PTR_ERR(leds_class_dev[0]);
    //      1,2,3对应led1/2/3设备节点
    for (minor=1; minor<4; ++minor)
    {
        leds_class_dev[minor] = class_device_create(leds_class, NULL, MKDEV(major, minor), NULL, "led%d", minor); 
        if (unlikely(IS_ERR(leds_class_dev[minor])))
            return PTR_ERR(leds_class_dev[minor]);
    }    
    printk(DEVICE_NAME " device initialized successfully...\n\n");

    return 0;
}
// 出口函数
static void leds_exit(void)
{
    int minor = 0; // 保存次设备号

    // 对应卸载
    unregister_chrdev(major, DEVICE_NAME);
    for (minor=0; minor<4; ++minor)
    {
        class_device_unregister(leds_class_dev[minor]); 
    }    
    class_destroy(leds_class);
    // 去掉映射关系
    iounmap(gpfcon);
}

// 内核模块相关
module_init(leds_init);
module_exit(leds_exit);
MODULE_LICENSE("GPL");

2. Makefile

KERN_DIR := /home/wuyexkx/Desktop/韦东山/system/linux-2.6.22.6

PWD := $(shell pwd)		# 执行pwd命令并把结果赋给PWD

obj-m := led_chrdev2.o  # .ko的生成依赖于.o,.o默认依赖.c

all:
	make -C $(KERN_DIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm # modules为编译目标
clean:
	make -C $(KERN_DIR) M=$(PWD) clean

3. 用户user_led2.c

#include <fcntl.h>
#include <stdio.h>
#include <string.h>

void print_usage(const char *argv0)
{
    printf("Usage:\n");
    printf("\t%s led<x> <on|off>\t[x=0~3]\n", argv0);
    printf("Example:\n");
    printf("\t%s led0 on\n", argv0);
    printf("\t%s led0 off\n\n", argv0);
}

// main传入3个参数 user_led2 led0 on/off
int main(int argc, char **argv)
{
    int fd;
    int is_off = 1;

    // main参数个数判断
    if (argc != 3)
    {
        print_usage(argv[0]);
        return 0;
    }  

    // 保存设备节点名称
    char device_name[10] = "/dev/";
    // 根据用户输入得到完整设备节点名称 "/dev/" + "led0" = "/dev/led0"
    strcat(device_name, argv[1]);
      
    // 打开 mdev根据驱动程序中的class_device_create自动创建的 设备节点
    fd = open(device_name, O_RDWR);
    if(fd < 0)
    {
        printf("can't open %s.\n", argv[1]);
        return 0;
    }    
    // 第三参数判断
    if(0 == strcmp(argv[2], "on"))      // 开灯参数
    {
        is_off = 0;
        // 根据第三参数write
        write(fd, &is_off, 1);
    }
    else if(0 == strcmp(argv[2], "off"))// 关灯参数
    {
        is_off = 1;
        // 根据第三参数write
        write(fd, &is_off, 1);
    }
    else                                // 第三参数错误
    {
        printf("Unknown args '%s'.\n\n", argv[2]);
    }
    
    return 0;
}

4. 插入内核模块,运行用户代码,点亮和关闭对应led

插入内核模块,显示初始化成功,并在proc/devices中看到为leds设备驱动分配的主设备号252,
在这里插入图片描述 在这里插入图片描述
dev/中看到四个对应的设备节点,依次申请到的次设备号0/1/2/3,都是252主设备号,
在这里插入图片描述
在这里插入图片描述
运行用户代码,打印帮助信息,打开led对应的设备节点;第三参数错误也打印出来。
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值