新字符设备开发
之前对于设备字符注册和申请分别采用的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