Bootstrap

标准驱动开发(Linux2.6(cdev) 的开发)

Linux2.6(cdev) 的开发

目录

Linux2.6(cdev) 的开发

回顾

Linux2.6(cdev) 的开发

了解一下 Linux2.6 开发框架

学习 Linux2.6 的相关接口

1、申请设备号(alloc_chrdev_region)

2、初始化 cdev 结构体(cdev_init)

3、注册设备(cdev_add)

编写一个 Linux2.6 的代码->蜂鸣器驱动

手动生成设备文件的指令和接口

结合 Linux2.6 写一个自动生成设备文件的驱动

1、生成设备文件所需要的类结构体(class_create)

2、生成设备文件(device_create)

补充:那个卸载函数采用倒序

小结:

编写一个 Linux2.6 的代码->蜂鸣器驱动(整体代码)


回顾

-- 1:何为杂项

miscdevice 不想分类的设备!
这种方法注册驱动设备目前是最简单!
注册完毕后自动生成了一个设备文件!
实际上其他的注册方法则不会!

-- 2:什么是驱动、Linux 驱动有什么特点

我认为驱动是:驱使硬件正常工作的代码或者程序
通用、统一接口、设备抽象成文件!

-- 设备文件有什么特点

属于系统的特殊文件之一
需要用非缓冲区操作
一般在/dev/xxxxx
有设备号:uint32_t 数字
分为 主设备号(高 12bit)
次设备号(低 20bit)

-- 什么是特殊文件?(普通人见不到的文件,不是开发人员见不到)

-- 特殊文件需要非缓冲区操作?

-- 普通文件都是需要缓冲区,因为容易造成浪费?

-- 为什么杂项注册时候,只提供次设备号
因为主设备号固定为 10

-- 主设备号和次设备号都是0-255(但这是32位的芯片,64位的不一定)


-- 何为 GPIO 子系统?
中间层:系统内核自己编写的统一接口

-- GPIO 子系统的 gpio 编号是如何计算的!

瑞芯微: 32大组号 + 8 小组号 + 本身编号


-- !!!A系列的芯片要取代单片机(比如人脸识别,单片机就不可以)

-- 目前海思,瑞星微,全智这几个厂商比较可以


Linux2.6(cdev) 的开发

-- 这是最标准的驱动开发

-- 对比于杂项而言,Linux2.6 的开发:

  • 1:开发较为复杂一些
  • 2:设备号需要用户自己申请
  • 3:设备注册完毕后不会生成,需要用户自己生成

其他特点完全等于与杂项设备,同样设备号不再限制了!

了解一下 Linux2.6 开发框架

  • 1:先去申请设备号-> alloc_chrdev_region->连续申请多个设备号
  • 2:再去拿申请的设备号注册设备->连续注册多个设备 cdev_init cdev_add
  • 3:Linux2.6 注册完毕后 不会生成设备文件的,我们需要手动生成 class_create device_create

alt text

alt text

学习 Linux2.6 的相关接口

1、申请设备号(alloc_chrdev_region)

-- 函数的功能:在内核中 申请一个/多个可用的设备号

-- 函数的头文件:<linux/fs.h>

-- 函数的原型:int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name);

-- 函数的参数:

  • dev_t *dev:申请的设备号,用户自己定义一个变量,函数会自动赋值!(dev_t == unsigned int)

就是你申请得到设备号存放的位置! 如果你申请多个设备号,那么它只存放你申请得到的第一个设备号!

  • baseminor:次设备号,你打算从哪个次设备号开始申请,随便填写 ,可以填写99

  • count:你申请得到的数量

举个例子:如果 你 baseminor 填的是 99 count 填写的是 3
如果此时系统返回的设备号主设备号 为 88
那么 就代表你申请了三个设备号返回了:

主设备号      次设备号        
88            99 ->第一个设备号存放于 第一个参数空间
88            100
88            101
  • label:标签是无所谓的一个名字

-- 函数返回值:

  • 如果申请成功返回0
  • 申请失败返回非 0
2、初始化 cdev 结构体(cdev_init)

-- 函数的功能:初始化 cdev 结构体

-- 函数的头文件:<linux/cdev.h>

-- 函数的原型:void cdev_init(struct cdev *cdev, const struct file_operations *fops);

-- 函数的参数:

  • cdev:用户自己定义一个变量,函数会自动赋值!

类似昨天的 杂项结构体
不同的是 这个结构体不需要像杂项一样,需要我们自己初始化
这个结构体你只需要申请这样的变量、或者开辟这样的空间
传入该函数里面即可!
靠函数做初始化工作!
就是 cdev 核心结构体->Linux2.6 核心结构体

  • fops:
    大家都在杂项见过,他就是内核层的文件接口

-- 函数返回值:无

3、注册设备(cdev_add)

-- 函数的功能:注册设备

-- 函数的头文件:<linux/cdev.h>

-- 函数的原型:int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);

-- 函数的参数:

  • cdev:毫无疑问,他就是你已经完成初始化工作的 cdev 核心结构体

  • dev:你要告诉我你接下来注册的设备的!!!!->首设备号

  • count: 你要连续注册几个设备!

举个例子:
如果你提供的 dev : 主设备号为
88 次设备号 88
你的 count 提供的是 3
那么 你将向内核连续注册三个设备:
分别为:

主设备号    次设备号
88          88
88          89
88          90

-- 函数返回值:

  • 成功返回 0
  • 失败返回 非 0

编写一个 Linux2.6 的代码->蜂鸣器驱动

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>

dev_t devnumber;
struct cdev mydev;

struct file_operations ops;

int mybeep_open(struct inode *i, struct file *f)
{

    gpio_set_value(36,1);
    return 0;
}

int mybeep_close(struct inode *i, struct file *f)
{
    gpio_set_value(36,0);
    return 0;
}

static int __init mybeep_init(void)
{
    //1:申请一个可以用的设备号
    int ret = alloc_chrdev_region(&devnumber,88,1,"beep");
    if(ret < 0 )
	{
		printk("申请失败!\r\n");
		return -EINVAL;//如果你返回的是这个东西则代表加载失败  insmod加载失效
	}
	printk("申请成功,mydevnum = %d\r\n",devnumber);
	printk("主设备号 === %d\r\n",devnumber >> 20);//主设备号是设备号的高12位,现在将设备号向右移20位,将高12位放在低位,得到主设备号
	printk("次设备号 === %d\r\n",devnumber & 0xFFFFF);//次设备号是设备号的低20位,这里是将低20位与出来,然后将高12位清0.(16进制一个F是4位,2*5 = 20位)

    //2:初始化  cdev 
    ops.owner = THIS_MODULE;
    ops.open = mybeep_open;
    ops.release = mybeep_close;

    cdev_init(&mydev,&ops);
    //3:往内核里面添加cdev 结构体
    ret = cdev_add(&mydev,devnumber,1);
    if(ret <0)
    {
        printk("注册设备失败!\r\n");
		return  -EINVAL;

    }
    printk("设备注册成功!\r\n");

    //4:申请GPIO 设置GPIO

    gpio_request(36,"beep");

    gpio_direction_output(36,0);




    return 0;
}

static void __exit mybeep_exit(void)
{

}

module_init(mybeep_init);
module_exit(mybeep_exit);

MODULE_LICENSE("GPL");


-- 编译成功后,加载驱动程序,查看设备号!

alt text

alt text

-- 连接串口

-- 可以看出正常生成了设备号

alt text

-- 但是查看设备文件,发现没有生成设备文件!

alt text

ls /dev/myb*

-- 可以看出虽然注册到了设备号,但是没有生成设备文件!,所以我们需要手动生成设备文件!

手动生成设备文件的指令和接口

 mknod /dev/mybeep c 234 88

-- 然后在main.c中写入打开这个设备文件

alt text


-- 但是手动生成设备文件没有什么意义,我们还是需要写一个驱动程序,自动生成设备文件!

结合 Linux2.6 写一个自动生成设备文件的驱动

1、生成设备文件所需要的类结构体(class_create)

-- 函数的功能:生成一个类结构体

-- 函数的头文件:<linux/device.h>

-- 函数的原型:struct class *class_create(struct module *owner, const char *name);

-- 函数的参数:

  • owner:只要你在内核见到这个东西,填写 THIS_MODULE

  • name:无所谓,生成类的标识名字

-- 函数返回值:返回的就是你创建的类结构体, 这个类结构体用于生成设备文件的函数的传参!

2、生成设备文件(device_create)

-- 函数的功能:在内核里面创建一个设备文件

-- 函数的头文件:<linux/device.h>

-- 函数的原型:struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

-- 函数的参数:

  • class:他就是刚才我们所讲到 类结构体,class_create 创建的结构体‘

  • parent:父设备,一般填写 NULL

  • devt:你要创建的设备文件的设备号

  • drvdata:你要创建的设备文件的设备号

  • fmt:printf();这就是你的 printf 格式化传参字符串!
    可以直接传字符串
    他就是你的生成的设备文件的名字
    只不过支持格式化传递!

-- 函数返回值:
用于设备的销毁 也可以不要

-- 在代码中加入

    //4:先去搞一个 类结构体
    cls = class_create(THIS_MODULE,"beep19");

    //5:生成设备文件
    device_create(cls,NULL,devnumber,NULL,"xydbeep");

补充:那个卸载函数采用倒序

-- 也就是先申请设备号函数,就最后取消申请设备号函数。

--

alt text

static void __exit myled_exit()
{
	//符合倒叙
	gpio_free(36);
	device_destroy(cls,mydevnum);
	class_destroy(cls);
		
	cdev_del(&mycdev);
	unregister_chrdev_region(mydevnum,1);

}


小结:
  • 申请设备号函数:

int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);

  • 取消申请的设备号

void unregister_chrdev_region(dev_t, unsigned);

  • cdev 的结构体的初始化

cdev_init

  • cdev 结构体的注册

cdev_add

  • cdev 结构体删除

cdev_del

  • 类结构体的创建

class_create(THIS_MODULE,"xyd_class");

  • 类结构体的销毁

class_destroy(cls);

  • 设备文件的创建

device_create(cls,NULL,mydevnum,NULL,"xydbeep");
//就会生成一个 设备文件 /dev/xydbeep

  • 设备文件的销毁

device_destroy(cls,mydevnum);

编写一个 Linux2.6 的代码->蜂鸣器驱动(整体代码)

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>

dev_t devnumber;
struct cdev mydev;

struct file_operations ops;

struct class * cls;//类结构体


int mybeep_open(struct inode *i, struct file *f)
{

    gpio_set_value(36,1);
    return 0;
}

int mybeep_close(struct inode *i, struct file *f)
{
    gpio_set_value(36,0);
    return 0;
}

static int __init mybeep_init(void)
{
    //1:申请一个可以用的设备号
    int ret = alloc_chrdev_region(&devnumber,88,1,"beep");
    if(ret < 0 )
	{
		printk("申请失败!\r\n");
		return -EINVAL;//如果你返回的是这个东西则代表加载失败  insmod加载失效
	}
	printk("申请成功,mydevnum = %d\r\n",devnumber);
	printk("主设备号 === %d\r\n",devnumber >> 20);//主设备号是设备号的高12位,现在将设备号向右移20位,将高12位放在低位,得到主设备号
	printk("次设备号 === %d\r\n",devnumber & 0xFFFFF);//次设备号是设备号的低20位,这里是将低20位与出来,然后将高12位清0.(16进制一个F是4位,2*5 = 20位)

    //2:初始化  cdev 
    ops.owner = THIS_MODULE;
    ops.open = mybeep_open;
    ops.release = mybeep_close;

    cdev_init(&mydev,&ops);
    //3:往内核里面添加cdev 结构体
    ret = cdev_add(&mydev,devnumber,1);
    if(ret <0)
    {
        printk("注册设备失败!\r\n");
		return  -EINVAL;

    }
    printk("设备注册成功!\r\n");

    //4:先去搞一个 类结构体
    cls = class_create(THIS_MODULE,"beep19");

    //5:生成设备文件
    device_create(cls,NULL,devnumber,NULL,"xydbeep");


    //6:申请GPIO 设置GPIO

    gpio_request(36,"beep");

    gpio_direction_output(36,0);




    return 0;
}

static void __exit mybeep_exit(void)
{
    gpio_free(36);

    device_destroy(cls,devnumber);

    class_destroy(cls);

    cdev_del(&mydev);
	unregister_chrdev_region(devnumber,1);


}

module_init(mybeep_init);
module_exit(mybeep_exit);

MODULE_LICENSE("GPL");


-- main.c


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

int main()
{
    while(1)
    {
        int fd = open("/dev/xydbeep",O_RDWR);//
        sleep(1);
        close(fd);
        sleep(1);
    }
    return 0;

}

-- 效果图

-- 可以看出自动生成了设备文件

alt text

-- 执行main,蜂鸣器响

alt text

;