STM32MP157 Linux驱动开发——新字符设备API

本文介绍了如何在新字符设备驱动中使用alloc_chrdev_region和register_chrdev_region动态分配和注册设备号,以及通过cdev_init初始化字符设备。还展示了如何创建和卸载设备驱动,以及实践应用中的设备挂载和查看操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

新字符设备开发

之前对于设备字符注册和申请分别采用的register_chrdev以及unregister_chrdev,而使用上述API接口函数需要自己手动查看那些设备号没有被占用,这样就导致需要浪费额外时间去查看,在使用新字符设备驱动API就可以完美接近找个问题。

alloc_chrdev_region和register_chrdev_region挂载函数

如果在开发驱动过程中没有指定设备号可以用alloc_chrdev_region函数进行动态申请注册,如果已经指定了设备号可以使用register_chrdev_region进行注册

                    示例代码 22.1.1.1 新字符设备驱动下设备号分配
int major; /* 主设备号 */
int minor; /* 次设备号 */
dev_t devid; /* 设备号 */

if (major) 
{ /* 定义了主设备号 */
    devid = MKDEV(major, 0); /* 大部分驱动次设备号都选择 0 */
    register_chrdev_region(devid, 1, "test");
} else 
{ /* 没有定义设备号 */
    alloc_chrdev_region(&devid, 0, 1, "test"); /* 申请设备号 */
    major = MAJOR(devid); /* 获取分配号的主设备号 */
    minor = MINOR(devid); /* 获取分配号的次设备号 */
}

unregister_chrdev_region卸载驱动函数

unregister_chrdev_region(led_devinfo.dev, 1);

新字符设备注册

void cdev_init(struct cdev *cdev, const struct file_operations *fops)初始化字符设备
参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。

                    示例代码 22.1.2.2 cdev_init 函数使用示例代码
struct cdev testcdev;
 
 /* 设备操作函数 */
static struct file_operations test_fops = {
    .owner = THIS_MODULE,
    /* 其他具体的初始项 */
 };
 
 testcdev.owner = THIS_MODULE;
 cdev_init(&testcdev, &test_fops); /* 初始化 cdev 结构体变量 */

创建类以及删除类函数

struct class *class_create (struct module *owner, const char *name)创建类函数

class_create 一共有两个参数,参数 owner 一般为 THIS_MODULE,参数 name 是类名字。
返回值是个指向结构体 class 的指针,也就是创建的类。

void class_destroy(struct class *cls);卸载类函数
参数 cls 就是要删除的类。

实际应用

驱动代码编写,驱动和上一期基本一致,不同的是无需手动创建节点以及查看设备号

#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 <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/mdev.h>

#define LED_Major   200
#define LED_Name    "LED_Device"
#define License     "GPL"
#define Author      "zhangzejun"

#define GPIOI_CLK           0X50000000
#define GPIOI_BASE          0x5000A000
#define RCC_MP_AHB4ENSETR   (GPIOI_CLK+0xA28)      //时钟地址
#define GPIOI_MODER         (GPIOI_BASE+0x00)       //模式地址
#define GPIOI_OTYPER        (GPIOI_BASE+0x04)       //输出模式地址       
#define GPIOI_OSPEEDR       (GPIOI_BASE+0x08)       //速度地址
#define GPIOI_PUPDR         (GPIOI_BASE+0x0C)       //上下拉地址
#define GPIOI_BSRR          (GPIOI_BASE+0x18)       //置位/复位地址

struct  led_dev
{
    int major;              //主设备号
    int minor;              //次设备号
    struct class* class;   //设备类
    struct device * device;
    struct cdev cdev;
    dev_t dev;         //设备

    /* data */
}led_devinfo;


static void __iomem  *RCC_MP_AHB4ENSETR_Virtual;
static void __iomem  *GPIOI_MODE_Virtual;
static void __iomem  *GPIOI_OTYPER_Virtual;
static void __iomem  *GPIOI_OSPEEDR_Virtual;
static void __iomem  *GPIOI_PUPDR_Virtual;
static void __iomem  *GPIOI_BSRR_Virtual;


static int led_open(struct inode *inode, struct file *filp)
{
	int res = 0;  
	return res;
}
static int led_release(struct inode *inode, struct file *filp)
{
	int res = 0;
	return res;
}

/*
* @description : LED初始化,申请虚拟地址
* @param - NULL : NULL
* @param - NULL : NULL
* @param - NULL : NULL
* @param - NULL : NULL
* @return : 写入的字节数,如果为负值,表示写入失败
*/
void Led_Init(void)
{
    u32  ahb4_clock_val = 0,mode_val = 0,otyper_val = 0,ospeed_val,pupdr_val = 0,bsrr_val = 0;
    RCC_MP_AHB4ENSETR_Virtual = ioremap(RCC_MP_AHB4ENSETR,4);
    GPIOI_MODE_Virtual        = ioremap(GPIOI_MODER,4);
    GPIOI_OTYPER_Virtual      = ioremap(GPIOI_OTYPER,4);
    GPIOI_PUPDR_Virtual       = ioremap(GPIOI_PUPDR,4);
    GPIOI_OSPEEDR_Virtual     = ioremap(GPIOI_OSPEEDR,4);
    GPIOI_BSRR_Virtual        = ioremap(GPIOI_BSRR,4);

    //初始化时钟
    ahb4_clock_val = readl(RCC_MP_AHB4ENSETR_Virtual);
    ahb4_clock_val &= ~(1 << 8);
    ahb4_clock_val |= (1 << 8);
    writel(ahb4_clock_val,RCC_MP_AHB4ENSETR_Virtual);

    //设置为输出模式
    mode_val =  readl(GPIOI_MODE_Virtual);
    mode_val &= ~(0x03 << 0);
    mode_val |= (0x01 << 0);
    writel(mode_val,GPIOI_MODE_Virtual); 

    //设置为推挽输出
    otyper_val = readl(GPIOI_OTYPER_Virtual);
    otyper_val &= 0xFFFFFFFE;
    writel(otyper_val,GPIOI_OTYPER_Virtual);

    //设置GPIO速度
    ospeed_val = readl(GPIOI_OSPEEDR_Virtual);
    mode_val &= ~(0x03 << 0);
    ospeed_val |= 0x01;
    writel(ospeed_val,GPIOI_OSPEEDR_Virtual);  

    //设置GPIO不上拉也不下拉
    pupdr_val = readl(GPIOI_PUPDR_Virtual);
    pupdr_val &= 0xFFFFFFFC;
    writel(pupdr_val,GPIOI_PUPDR_Virtual);

    //初始化关闭LED灯
    bsrr_val = readl(GPIOI_BSRR_Virtual);
    bsrr_val |= 0x01;
    writel(bsrr_val,GPIOI_BSRR_Virtual);
}


/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/

static ssize_t led_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{
	int res=0;
    char led_val[1] ={0};
    u32  set_val = 0;
	res = copy_from_user(led_val,buf,cnt);
	if(res >= 0)
	{
        if(led_val[0] == '0')
        {
            set_val = readl(GPIOI_BSRR_Virtual);
            set_val &= 0xFFFFFFFE;
            set_val |= (0x1 << 16);           
            writel(set_val,GPIOI_BSRR_Virtual); 
        }
        else if(led_val[0] == '1')
        {
            set_val = readl(GPIOI_BSRR_Virtual);
            set_val &= 0xFFFEFFFF;
            set_val |= 0x01;
            writel(set_val,GPIOI_BSRR_Virtual); 
        }      
	}
	else
	{
		printk("write led error\r\n");
	}
	return res;
}

void led_unmap(void)
{
    iounmap(RCC_MP_AHB4ENSETR_Virtual);
    iounmap(GPIOI_MODE_Virtual);
    iounmap(GPIOI_OTYPER_Virtual);
    iounmap(GPIOI_PUPDR_Virtual);
    iounmap(GPIOI_OSPEEDR_Virtual);   
    iounmap(GPIOI_BSRR_Virtual);  
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt,loff_t *offt)
{
	int res = 0;
    return res;
}

static const struct file_operations led_fops = {
	.owner =	THIS_MODULE,
	.read =		led_read,
	.write =	led_write,
	.open =		led_open,
	.release =	led_release,
};

static int __init led_init(void)
{
	int res = 0;
     Led_Init();
     memset(&led_devinfo,0,sizeof(led_devinfo));    //清除结构体
     if(led_devinfo.major == 0)
     {
          res = alloc_chrdev_region(&led_devinfo.dev,0,1,LED_Name);
     }
     led_devinfo.major = MAJOR(led_devinfo.dev);
     led_devinfo.minor = MINOR(led_devinfo.dev);

     led_devinfo.cdev.owner = THIS_MODULE;
     cdev_init(&led_devinfo.cdev, &led_fops);
     res = cdev_add(&led_devinfo.cdev, led_devinfo.dev, 1);
     if(res < 0)
     {
         goto fail_cdevadd;
     }

    if(res < 0)
    {
        printk("led init error\r\n");
        goto fail_map;
    }
    else
    {
        printk("device Mjor id %d,Minor %d\r\n",led_devinfo.major,led_devinfo.minor);
    }
    
    led_devinfo.class =  class_create(THIS_MODULE,LED_Name);
    if (IS_ERR(led_devinfo.class))
    {
        goto fail_class;
    }
    led_devinfo.device = device_create(led_devinfo.class,NULL,led_devinfo.dev,NULL,LED_Name);
    if (IS_ERR(led_devinfo.device))
    {
        goto fail_device;
    }
	printk("led init\r\n");
	return res;

fail_cdevadd:
    unregister_chrdev_region(led_devinfo.dev, 1);
    return -1;
fail_device:
    device_destroy(led_devinfo.class, led_devinfo.device);
    return -1;
fail_class:
    class_destroy(led_devinfo.class);
    return -1;
fail_map:
    led_unmap();
    return -1;
}

static void __exit led_exit(void)
{
    //取消申请的物理地址
    led_unmap();

   /* 删除 cdev */
    cdev_del(&led_devinfo.cdev);

    //卸载驱动
    unregister_chrdev_region(led_devinfo.dev, 1);

    //删除设备
    device_destroy(led_devinfo.class, led_devinfo.dev);
    //删除类
    class_destroy(led_devinfo.class);
	printk("led exit\r\n");
}

module_init(led_init);			 //驱动注册函数
module_exit(led_exit);			//驱动卸载函数

MODULE_LICENSE(License); 					
MODULE_AUTHOR(Author);			
MODULE_INFO(intree,"Y");

实践应用

将编译好的文件放到开发板下

shishukeya@ubuntu:~/linux/Linux_Drivers/03_newled$ sudo cp led.ko APP  /home/shishukeya/linux/nfs/rootfs/lib/modules/5.4.31 -f

挂载驱动,可以看到打印了驱动主设备号以及次设备号


[root@ATK-stm32mp1]:/lib/modules/5.4.31$ modprobe led.ko
device Mjor id 241,Minor 0
led init

查看文件也是存在

[root@ATK-stm32mp1]:/lib/modules/5.4.31$ ls /dev/LED_Device
/dev/LED_Device

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值