Linux驱动12 --- Linux2.6 开发方法&文件接口

目录

[一、Linux2.6 字符设备开发](#一、Linux2.6 字符设备开发)

[1.1 介绍](#1.1 介绍)

[1.2 特点](#1.2 特点)

[1.3 相关 API](#1.3 相关 API)

[Linux2.6 的开发过程](#Linux2.6 的开发过程)

添加头文件

申请设备号

释放设备号

[Linux2.6 核心结构体初始化](#Linux2.6 核心结构体初始化)

[Linux2.6 核心结构体添加](#Linux2.6 核心结构体添加)

[Linux2.6 核心结构体删除](#Linux2.6 核心结构体删除)

[1.4 通过指令生成设备文件](#1.4 通过指令生成设备文件)

[1.5 自动生成设备文件](#1.5 自动生成设备文件)

设备创建

类的创建

设备的销毁

类的销毁

二、文件接口

[2.1 read 文件接口](#2.1 read 文件接口)

[read 的实现函数](#read 的实现函数)

[2.2 write文件接口](#2.2 write文件接口)

[write 的实现函数](#write 的实现函数)

[2.3 ioctl](#2.3 ioctl)

应用接口

内核接口

[三、GPIO 子系统](#三、GPIO 子系统)

[设置 GPIO 为输入方向](#设置 GPIO 为输入方向)

[获取 GPIO 当前的电平状态](#获取 GPIO 当前的电平状态)


一、Linux2.6 字符设备开发

1.1 介绍

驱动开发方式之一

1.2 特点

1、真正意义上的 32 位设备号

2、设备号需要开发者申请/释放

3、无法自动生成设备文件

4、相对比较复杂

1.3 相关 API

关键字:cdev

头文件:#include <linux/cdev.h>

#include <linux/fs.h>

Linux2.6 的开发过程

添加头文件
申请设备号

函数原型

int alloc_chrdev_region(dev_t *dev, unsigned int baseminor, unsigned int count, const char *name)

函数参数

dev:设备号存放的地址,是连续开辟的

baseminor:申请的次设备号起始值

次设备号是我们指定的,主设备号是系统分配的

count:连续申请次设备号的个数

例如:第二个参数填 54,第三个参数填 2

那么会得到两个设备号,其中第一个次设备为 54,第二个次设备号为 55

name:无所谓,尽可能有意义 ,申请设备号操作,这个 name 不要重复

函数返回值

成功返回 0,失败返回负数

释放设备号

函数原型

void unregister_chrdev_region(dev_t dev, unsigned int count)

函数参数

dev:要释放设备号的起始值

count:释放的个数

Linux2.6 核心结构体初始化

函数原型

void cdev_init(struct cdev *cdev, const struct file_operations *ops)

函数参数

cdev:Linux2.6 核心结构体

复制代码
struct cdev
{

    const struct file_operations *ops; //文件接口核心结构体

    dev_t dev;                        //起始设备号
    
    unsigned int count;               //设备号连续的数量

    struct module *owner;             //固定填写

} 

加粗的部分都是通过函数去填写的,我们只需要给 owner 赋值

Linux2.6 核心结构体添加

函数原型

int cdev_add(struct cdev *cdev, dev_t dev, unsigned int count)

函数参数

cdev:Linux2.6 核心结构体

dev:起始设备号

count:设备号的个数

函数返回值

成功返回 0,失败返回负数

Linux2.6 核心结构体删除

函数原型

void cdev_del(struct cdev *cdev)

函数参数

cdev:Linux2.6 核心结构体

1.4 通过指令生成设备文件

1.5 自动生成设备文件

设备创建

头文件:#include <linux/device.h>

函数原型

struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void

*drvdata, const char *fmt, ...)

函数参数

cls:是一个类 --- 这个类只需要调用函数生成即可

parent:设备的上一级,但是我们的设备是你我创建,没有填 NULL

devt:设备使用的设备号

drvdata:传递给内核的参数,不使用填 NULL

fmt:可以填格式控制符,也可以直接填一个字符串作为文件的名字

函数返回值

无所谓,不用

类的创建

函数原型

class_create(owner, name)

函数参数

struct module *owner:固定填写

const char *name:无所谓,尽量有意义,不要重复

函数返回值

类的核心结构体,struct class

设备的销毁

函数原型

void device_destroy(struct class *cls, dev_t devt)

函数参数

cls:类

devt:设备号

类的销毁

函数原型

void class_destroy(struct class *cls)

函数参数

cls:类

二、文件接口

对于系统层来说:read 函数可以获取一个数据,数据由内核产生,所以对于内核来说本质上是输出了一个数据

对于系统层来说:wirt 函数可以写入一个数据,数据由内核接收,所以对于内核来说本质上是获取了一个数据

2.1 read 文件接口

函数原型

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

函数参数

struct file:用于多节点判断

char __user *:应用层 read 函数存放数据空间的首地址

size_t:获取内容的长度

loff_t *:用在块设备,指代偏移量

read 的实现函数

头文件:#include <linux/uaccess.h>

函数原型

static inline int copy_to_user(void __user volatile *to, const void *from, unsigned long n)

函数参数

to:数据到哪里去 --- 直接填写(*read)的第二个参数

from:数据从哪里来 --- 内核定义一个变量,然后取地址即可

n:数据的长度 --- 直接填写(*read)的第三个参数

函数返回值

必须承接,否则会报警告,而警告会被当成错误

2.2 write文件接口

函数原型

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

函数参数

struct file:用于多节点判断

const char __user *:应用层 write 函数存放数据空间的首地址

size_t:获取内容的长度

loff_t *:用在块设备,指代偏移量

write 往内核中写数据,内核需要做接收

write 的实现函数

函数原型

static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n)

函数参数

to:数据到哪里去 --- 数据要到内核

内核就需要定义一个变量去承接 wirte 写入的数据

from:数据从哪里来 --- (*write)第二个参数

n:传入数据的长度

函数返回值

必须承接,否则会报错

2.3 ioctl

应用接口

头文件

#include <sys/ioctl.h>

函数原型

int ioctl(int fd, unsigned long request, ...)

函数参数

fd:文件描述符

request:就是传递给内核的命令

...:表示可变参数

可变参数具体是什么由第二个参数命令决定

函数返回值

成功返回 0,失败返回负数

内核接口

函数原型

long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long n);

函数参数

struct file:用于多节点判断

cmd:命令 --- 本质上是一个数字,但是数据传递无法直接传递数字

所以这个命令其实就是一个宏定义

命令中,2 值已经被系统保留(我们没法用)

n:数据长度

ioctl 的应用主要在:标准文件接口

例如:

在 V4L2 摄像头开发中用于获取摄像头参数,以及设置摄像头参数,在屏幕开发中用于获取屏幕的参数

三、GPIO 子系统

设置 GPIO 为输入方向

函数原型

int gpio_direction_input(unsigned gpio)

函数参数

gpio:要设置的 GPIO 号

函数返回值

成功返回 0,失败返回负数

获取 GPIO 当前的电平状态

函数原型

int gpio_get_value(unsigned gpio)

函数参数

gpio:要获取电平状态的 GPIO 号

函数返回值

高电平,就返回 1,低电平返回 0