字符设备驱动

一、字符设备驱动的设计流程

-------定义并初始化一个字符设备---------
1、定义一个字符设备—>struct cdev
2、定义并初始化字符设备的文件操作集—>struct file_operations
3、给字符设备申请一个设备号—>设备号=主设备号<<20 + 次设备号
4、初始化字符设备
5、将字符设备加入内核
-------自动生成设备文件---------
6、创建class
7、创建device,其中device是属于class的
-------得到物理地址对应的虚拟地址-------
8、申请物理内存区,申请SFR的地址区。SFR — Special Function Register: GPIOEOUT
9、内存的动态映射,得到物理地址对应的虚拟地址
10、访问虚拟地址

二、定义一个字符设备

1.描述字符设备的结构体–cdev

#include <linux/cdev.h>
	struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

在linux内核中,使用cdev来描述一个字符设备,每个字符设备都有一个自己的cdev。设计字符设备首先定义一个cdev。
例:

struct cdev gec6818_led_cdev;

2.cdev的成员

struct kobject kobj; --->内核管理驱动的时候,使用的一个object
struct module *owner; --->cdev是属于哪个module,一般写成THIS_MODULE
const struct file_operations *ops; --->cdev的文件操作集
struct list_head list; --->内核管理cdev的链表
dev_t dev; --->设备号
unsigned int count; --->次设备的数量

三、定义并初始化一个文件操作集

1.文件操作集

struct file_operations {
	struct module *owner;
	...............................
	ssize_t (*read) (struct file *, char __user *, 	size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*release) (struct inode *, struct file *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	..............................
}

2.文件操作集的作用

每个cdev都有一个文件操作集,文件操作集是驱动程序给应用程序提供的接口。应用程序open()会找到驱动程序的open(),驱动程序的open()可以用来访问硬件。

3.例子

int gec6818_led_open(struct inode *inode, struct file *filp)
{
    return 0;
}
ssize_t gec6818_led_read(struct file *filp, char __user *user_buf, size_t size, loff_t *off)
{
}
ssize_t gec6818_led_write(struct file *filp, const char __user *user_buf, size_t size, loff_t *off)
{
}
int gec6818_led_release(struct inode *inode, struct file *filp)
{
    return 0;
}
static const struct file_operations gec6818_led_fops = {
    .owner = THIS_MODULE,
    .open = gec6818_led_open,
    .read = gec6818_led_read,
    .write = gec6818_led_write,
    .release = gec6818_led_release,
};
**ioctl用法**
IOW(copy_from_user)  IOR(copy_to_user)

驱动程序:
#define SWCSKCONTROL_IOC_CSK6012_RESET _IOW('A', 0x11, int)
#define SWCSKCONTROL_IOC_CSK6012_WAKE_HOST _IOW('A', 0x12, int)
#define SWCSKCONTROL_IOC_CSK6012_MUTE _IOW('A', 0x13, int)
#define SWCSKCONTROL_IOC_CSK6012_SEL _IOW('A', 0x14, int)
#define SWCSKCONTROL_IOC_CSK6012_AMP_MUTE _IOW('A', 0x15, int)
#define SWCSKCONTROL_IOC_HP_MUTE _IOW('A', 0x16, int)

static long swcskcontrol_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{

    pr_info("%s: cmd=%d, arg=%ld\n", __func__, cmd, arg);
    switch (cmd) {
    case SWCSKCONTROL_IOC_CSK6012_RESET:
        swcskcontrol_dev_reset(arg);
        break;
    case SWCSKCONTROL_IOC_CSK6012_WAKE_HOST:
        swcskcontrol_dev_wake_host(arg);
        break;
    case SWCSKCONTROL_IOC_CSK6012_MUTE:
        swcskcontrol_dev_mute(arg);
        break;
    case SWCSKCONTROL_IOC_CSK6012_SEL:
        swcskcontrol_dev_sel(arg);
        break;
    case SWCSKCONTROL_IOC_CSK6012_AMP_MUTE:
        swcskcontrol_dev_amp_mute(arg);
        break;
    case SWCSKCONTROL_IOC_HP_MUTE:
        swcskcontrol_dev_hp_mute(arg);
    default:
        break;
    }

    return 0;
}

应用程序:
#define SWCSKCONTROL_IOC_CSK6012_RESET _IOW('A', 0x11, int)
#define SWCSKCONTROL_IOC_CSK6012_WAKE_HOST _IOW('A', 0x12, int)
#define SWCSKCONTROL_IOC_CSK6012_MUTE _IOW('A', 0x13, int)
#define SWCSKCONTROL_IOC_CSK6012_SEL _IOW('A', 0x14, int)

ret = ioctl(fd, SWCSKCONTROL_IOC_CSK6012_RESET, reset);
ret = ioctl(fd, SWCSKCONTROL_IOC_CSK6012_WAKE_HOST, value);

inode结构体
inode结构表示打开的文件描述符,包含了大量有关文件的信息。
file结构体
file结构表示一个打开的文件,内核在open时创建,open时可以设备结构体(file->private_data = &swled;)为私有数据,并传递给该文件上进行操作的所有函数。
loff_t类型
loff_t表示当前读写位置

四、给字符设备申请一个设备号—dev_t dev

1.什么是设备号

每个设备文件(字符设备 or 块设备)都已一个设备号,相当于设备文件ID。
设备号有主设备号和次设备号组成的。
设备号是一个32bits的无符号整型值。

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t    dev_t;

2.设备号运算的函数:

1)由主设备号和次设备号生成设备号

#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi)) //MINORBITS=20

2)由设备号得到主设备号和次设备号

#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))

3.主设备号和次设备号的作用

例:

crw-rw----    1 root     root      204,  64 Jan  1  1970 ttySAC0  串口0
crw-rw----    1 root     root      204,  65 Jan  1  1970 ttySAC1
crw-rw----    1 root     root      204,  66 Jan  1  1970 ttySAC2
crw-rw----    1 root     root      204,  67 Jan  1  1970 ttySAC3  串口3

主设备号描述一个硬件设备的类型:如uart、IIC、摄像头、…
次设备号描述这种硬件类型下的具体某个硬件

4.如何申请设备号

1)静态注册—>指定设备号,注册到内核中。如果内核已经使用该设备号,注册就不成功。

/**
register_chrdev_region() - register a range of device numbers
@from: the first in the desired range of device numbers; must include
   the major number.

@count: the number of consecutive device numbers required
@name: the name of the device or driver.

Return value is zero on success, a negative error code on failure.
例:
crw-rw----    1 root     root      204,  64 Jan  1  1970 ttySAC0  串口0
crw-rw----    1 root     root      204,  65 Jan  1  1970 ttySAC1
crw-rw----    1 root     root      204,  66 Jan  1  1970 ttySAC2
crw-rw----    1 root     root      204,  67 Jan  1  1970 ttySAC3  串口3
register_chrdev_region(MKDEV(204,64), 4, "ttySAC") //ttySAC --->设备名称
// /dev/ttySAC0 --->设备文件

2)动态分配—>内核自动分配空闲的设备号

/**
alloc_chrdev_region() - register a range of char device numbers
@dev: output parameter for first assigned number
@baseminor: first of the requested range of minor numbers
@count: the number of minor numbers required
@name: the name of the associated device or driver

Allocates a range of char device numbers.  The major number will be
chosen dynamically, and returned (along with the first minor number)
in @dev.  Returns zero or a negative error code.
参数说明:
dev_t *dev --->分配后的设备号
unsigned baseminor  --->次设备号的开始值
unsigned count --->次设备的数量
const char *name ---->设备名称,但不是设备文件的名字。#cat /proc/devices
返回值:
成功返回0,失败返回复数错误码。

3)设备号的注销

/**
unregister_chrdev_region() - return a range of device numbers
@from: the first in the range of numbers to unregister
@count: the number of device numbers to unregister

This function will unregister a range of @count device numbers,
starting with @from.  The caller should normally be the one who
allocated those numbers in the first place...

五、初始化字符设备

/**
cdev_init() - initialize a cdev structure
@cdev: the structure to initialize
@fops: the file_operations for this device

Initializes @cdev, remembering @fops, making it ready to add to the
system with cdev_add().
思考:
static struct cdev gec6818_led_cdev; //有内存
cdev_init(&gec6818_led_cdev, const struct file_operations *fops);
或:
static struct cdev *gec6818_led_cdev; //没有内存
cdev_init(gec6818_led_cdev, const struct file_operations *fops);//segment fault
ok:
static struct cdev *gec6818_led_cdev;
gec6818_led_cdev = (struct cdev *)kmalloc(sizeof(struct cdev), GFP_KERNEL)
if(gec6818_led_cdev == NULL){
}
cdev_init(gec6818_led_cdev, const struct file_operations *fops);//segment fault

六、将字符设备加入内核

/**
cdev_add() - add a char device to the system
@p: the cdev structure for the device
@dev: the first device number for which this device is responsible
@count: the number of consecutive minor numbers corresponding to this
    device


cdev_add() adds the device represented by @p to the system, making it
live immediately.  A negative error code is returned on failure.
/**
cdev_del() - remove a cdev from the system
@p: the cdev structure to be removed

cdev_del() removes @p from the system, possibly freeing the structure
itself.

七、创建class

创建class和device的目的是在安装的驱动的时候,可以自动生成设备文件,在卸载驱动的时候,可以自动的删除设备文件。
如果不自动生成设备文件:也可以手动创建:
#mkmod c /dev/led_drv 主设备号 次设备号
创建的class生成在:/sys/class/
#include <linux/device.h>

1.创建class

struct class *class_create(struct module *owner, const char *name)
参数说明:
struct module *owner --->创建的class属于哪个module,一般为THIS_MODULE。
const char *name --->自定义的class的名字
返回值:
得到的class

2.class的删除

void class_destroy(struct class *cls);

八、创建device

device是输于class的,当驱动程序有了class和device以后,内核使用mdev这个工具,根据class和device创建该驱动的设备文件。
创建的device怎么查看:/sys/class/***/

1.device的创建

/**
device_create - creates a device and registers it with sysfs
@class: pointer to the struct class that this device should be registered to
@parent: pointer to the parent struct device of this new device, if any
@devt: the dev_t for the char device to be added
@drvdata: the data to be added to the device for callbacks
@fmt: string for the device's name

This function can be used by char device classes.  A struct device
will be created in sysfs, registered to the specified class.

A "dev" file will be created, showing the dev_t for the device, if
the dev_t is not 0,0.
If a pointer to a parent struct device is passed in, the newly created
struct device will be a child of that device in sysfs.
The pointer to the struct device will be returned from the call.
Any further sysfs files that might be required can be created using this
pointer.

Returns &struct device pointer on success, or ERR_PTR() on error.

Note: the struct class passed to this function must have previously
been created with a call to class_create().

参数说明:
struct class *class —>device属于哪个class
struct device *parent —>device的父设备,一般为NULL
dev_t devt —>设备号
void *drvdata —>驱动的data,一般为NULL
const char *fmt —>设备文件的名字
返回值:
struct device * —>创建好的device

2.删除device

/**
device_destroy - removes a device that was created with device_create()
@class: pointer to the struct class that this device was registered with
@devt: the dev_t of the device that was previously registered

This call unregisters and cleans up a device that was created with a
call to device_create().
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值