Linux--字符设备驱动开发框架(二)

Linux–字符设备驱动开发框架(一)中已经梳理了一下字符设备驱动开发的基本流程,但是其中所用的函数不太符合现在开发习惯,有一些缺点。比如:
1)设备号分配
在(1)中需要我们手动定义一个主设备号,但是没法指定次设备号,就默认为0—0~1048575(2^20-1),完全被占用,造成极大浪费。除此之外,主设备号也不是我们随心所欲定义的,必须提前知道他有没有被占用。
2)设备节点
在(1)中设备节点还需要通过mknod手动创建加载,不人性化。
3)新的开发API
(1)分配设备号

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
			const char *name)
/* @dev: output parameter for first assigned number  即我们要创建的设备号
 * @baseminor: first of the requested range of minor numbers 次设备的申请起点(一般从0开始)
 * @count: the number of minor numbers required 连续申请的次设备个数(注意,主设备号一样)
 * @name: the name of the associated device or driver 设备名字
 * @Returns zero or a negative error code
 * /

(2)释放设备号

void unregister_chrdev_region(dev_t from, unsigned count)
//@from: the first in the range of numbers to unregister 直接就是前面申请的设备号
// @count: the number of device numbers to unregister    要释放的设备个数

举例

#define NEW_CHRDEV_NAME "new_chrdev"  //设备名字
#define DEV_COUNT //要申请的设备个数
dev_t devid;//dev_t 起始就是usigned int devid是设备号,包括了主设备号和次设备号
//申请设备号
alloc_chrdev_region(devid,0,DEV_COUNT,NEW_CHRDEV_NAME );
//我们可以取出来major,minor来看看
usigned int major;
usigned int minor;
major = MAJOR(devid);//取出主设备号
minor = MINOR(devid);//取出次设备号

//释放设备号
unregister_chrdev_region(devid,DEV_COUNT );

(3)新注册设备API

//需要用到以下两个函数配合使用
/*
初始化设备结构体
@cdev 要初始化的结构体
@fops 设备操作集合
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default);
	cdev->ops = fops;
}
/*
加载字符设备到系统中
@p 设备的cdev的结构体 在include/linux/cdev.h 
@dev 设备号
@count 申请的设备个数(次设备个数)
@失败时返回负值
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;

	p->dev = dev;
	p->count = count;

	error = kobj_map(cdev_map, dev, count, NULL,
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}
/*
卸载驱动的时候 从系统中移除设备
@p 指的就是cdev结构体
@dev 设备号
@count 要添加的设备数量
*/
void cdev_del(struct cdev *p)
{
	cdev_unmap(p->dev, p->count);
	kobject_put(&p->kobj);
}

(4)创建结构体

struct new_char_dev{
	struct cdev cdev;//上面初始化设备和加载时要用
	struct class *class; // 自动创建设备节点要用
	struct device *device;//自动创建设备节点要用
	dev_t devid;//设备号 
	usigned int major;
	usigned int minor;
}newchrdev;

//其中cdev结构体如下:
struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
};

(5)创建 file_operations

/*
字符设备操作集
里面的内容根据需要增添
*/
static const struct file_operations new_dev_fops = 
{
    .owner = THIS_MODULE,
    .write = led_write,
    .open = led_open,
    .release = led_release,
};

(6)自动创建设备节点

//第一步需要先创建一个class类,class是一个结构体
/*
@owner  THIS_MODULE
@name   设备名
&返回时一个class结构体
*/
#define class_create(owner, name)		\
({						\
	static struct lock_class_key __key;	\
	__class_create(owner, name, &__key);	\
})

struct class *__class_create(struct module *owner, const char *name,
			     struct lock_class_key *key)
/*
卸载驱动的时候要删除加载时创建的类
*/
void class_destroy(struct class *cls)

/*
第二步创建设备
@class 前面创建的
@parent 一般写NULL
@devt 设备号
@drvdata 一般写NULL
@fmt 设备名字
*/
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
{
	va_list vargs;
	struct device *dev;

	va_start(vargs, fmt);
	dev = device_create_vargs(class, parent, devt, drvdata, fmt, vargs);
	va_end(vargs);
	return dev;
}

/*
在卸载设备的时候 要删除设备节点
@class 创建的class
@devt 设备号
*/
void device_destroy(struct class *class, dev_t devt)
{
	struct device *dev;

	dev = class_find_device(class, NULL, &devt, __match_devt);
	if (dev) {
		put_device(dev);
		device_unregister(dev);
	}
}

整体举例

#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h> 
#include <linux/ide.h>
#include <linux/io.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/cdev.h>

#define NEW_CHRDEV_NAME "new_chrdev"  //设备名字
#define DEV_COUNT //要申请的设备个数

struct new_char_dev{
	struct cdev cdev;//上面初始化设备和加载时要用
	struct class *class; // 自动创建设备节点要用
	struct device *device;//自动创建设备节点要用
	dev_t devid;//设备号 
	usigned int major;
	usigned int minor;
}newchrdev;

static int newchrdev_open(struct inode *inode,struct file *filp)
{
    return 0;
}
static int newchrdev_release(struct inode *inode,struct file *filp)
{
    return 0;
}

static int newchrdev_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
       return 0;
}

/*字符设备操作集*/
static const struct file_operations led_new_dev_fops = 
{
    .owner = THIS_MODULE,
    .write = newchrdev_write,
    .open = newchrdev_open,
    .release = newchrdev_release,
};
/*入口*/
static int __init newchrdev_init(void)  //把初始化放在了入口函数,放在open也可以
{
	int ret;
	newchrdev.major = 0;
   //申请设备号
    if(newchrdev.major) //假设我们定义过设备号了
    {
        newchrdev.devid = MKDEV(newchrdev.major,0);//定义了主设备号 也就是将major 和minor(此处为0)组合成devid
        ret = register_chrdev_region(newchrdev.devid , DEV_COUNT  ,NEW_CHRDEV_NAME );//返回值为0成功,负值失败
    }else
    {
        ret = alloc_chrdev_region(&newchrdev.devid,0,DEV_COUNT ,NEW_CHRDEV_NAME );
        newchrdev.major = MAJOR(newchrdev.devid);//从devid中获取主设备号即高12位
        newchrdev.minor = MINOR(newchrdev.devid);//从devid中获取次设备号即低20位
    }
    printk("newdev_init\r\nnewdev major = %d, newdev minor = %d\r\n",newchrdev.major ,newchrdev.minor);
    if(ret < 0)
    {
        printk("register fail\r\n");
        return -EIO;//官方定义的错误标记
    }
     /*注册字符设备*/
    newchrdev.cdev.owner = THIS_MODULE;
    cdev_init(&newchrdev.cdev,&led_new_dev_fops);//初始化
    ret = cdev_add(&newchrdev.cdev,newchrdev.devid,1);//添加设备
    if(ret != 0)
        return  EIO;
    /*自动创建设备节点*/
    //1,创建类
    newchrdev.class = class_create(THIS_MODULE,NEW_CHRDEV_NAME );
    if(IS_ERR(newchrdev.class))
        return PTR_ERR(newchrdev.class);
    //2,创建设备
    newchrdev.device = device_create(newchrdev.class,NULL,newchrdev.devid,NULL,NEW_CHRDEV_NAME );
    if(IS_ERR(newchrdev.device))
        return PTR_ERR(newchrdev.device);
    return 0;
}

static void __exit newchrdev_exit(void)
{
     /*注销字符设备号及设备*/
    unregister_chrdev_region(newchrdev.devid, DEV_COUNT );
    //删除字符设备
    cdev_del(&newchrdev.cdev );
    //删除设备节点的类
    class_destroy(newchrdev.class);
    //删除设备
    device_destroy(newchrdev.class,newchrdev.devid);
    printk("exit\r\n");
}
/*函数出入口*/
module_init(newchrdev_init);
module_exit(newchrdev_exit);

/*协议和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("name");

补充
1)设置文件私有数据

//往往在open函数中加入
file->private_data = &newchrdev;
//file指的是open函数的形参
//newchrdev指的是我们在前面创建的结构体变量名

通过私有数据,就可以在其他函数中使用

struct new_char_dev *dev = file -> private_data;
//就可以使用dev->.... 访问结构体成员变量了

举例

static int newchrdev_open(struct inode *inode,struct file *filp)
{
	file->private_data = &newchrdev;
    return 0;
}
static int newchrdev_write(struct file *filp,const char __user *buf,size_t count,loff_t *ppos)
{
	struct new_char_dev *dev = file -> private_data;
	...
	return 0;
}

2)goto的用法
前面都是直接用return …进行操作
但是如果出现错误直接return 是不专业的
可以写相应的goto 函数对其进行处理
举例:

goto cs_release;
...
cs_release:
	cm4000_release(link);

就先写到这,有遗漏再补充
只为学习记录,有问题恳请各位老师批评指正
感激不尽

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
浅谈PCIe体系结构 浅谈PCIe体系结构 - 1 - 目录 - 1 - 第I篇PCI体系结构概述 - 1 - 第1章PCI总线的基本知识 - 3 - 1.1 PCI总线的组成结构 - 6 - 1.1.1 HOST主桥 - 6 - 1.1.2 PCI总线 - 7 - 1.1.3 PCI设备 - 7 - 1.1.4 HOST处理器 - 8 - 1.1.5 PCI总线的负载 - 9 - 1.2 PCI总线的信号定义 - 10 - 1.2.1 地址和数据信号 - 10 - 1.2.2 接口控制信号 - 11 - 1.2.3 仲裁信号 - 13 - 1.2.4 中断请求等其他信号 - 14 - 1.3 PCI总线的存储器读写总线事务 - 15 - 1.3.1 PCI总线事务的时序 - 15 - 1.3.2 Posted和Non-Posted传送方式 - 16 - 1.3.3 HOST处理器访问PCI设备 - 17 - 1.3.4 PCI设备读写主存储器 - 19 - 1.3.5 Delayed传送方式 - 21 - 1.4 PCI总线的中断机制 - 23 - 1.4.1 中断信号与中断控制器的连接关系 - 23 - 1.4.2 中断信号与PCI总线的连接关系 - 24 - 1.4.3 中断请求的同步 - 25 - 1.5 PCI-X总线简介 - 28 - 1.5.1 Split总线事务 - 28 - 1.5.2 总线传送协议 - 28 - 1.5.3 基于数据块的突发传送 - 29 - 1.6 小结 - 29 - 第2章PCI总线的桥与配置 - 30 - 2.1 存储器域与PCI总线域 - 30 - 2.1.1 CPU域、DRAM域与存储器域 - 31 - 2.1.2 PCI总线域 - 32 - 2.1.3 处理器域 - 32 - 2.2 HOST主桥 - 34 - 2.2.1 PCI设备配置空间的访问机制 - 35 - 2.2.2 存储器域地址空间到PCI总线域地址空间的转换 - 37 - 2.2.3 PCI总线域地址空间到存储器域地址空间的转换 - 39 - 2.2.4 x86处理器的HOST主桥 - 42 - 2.3 PCI桥与PCI设备的配置空间 - 45 - 2.3.1 PCI桥 - 45 - 2.3.2 PCI Agent设备的配置空间 - 47 - 2.3.3 PCI桥的配置空间 - 50 - 2.4 PCI总线的配置 - 54 - 2.4.1 Type 01h和Type 00h配置请求 - 54 - 2.4.2 PCI总线配置请求的转换原则 - 56 - 2.4.3 PCI总线树Bus号的初始化 - 58 - 2.4.4 PCI总线Device号的分配 - 60 - 2.5 非透明PCI桥 - 62 - 2.5.1 Intel 21555中的配置寄存器 - 63 - 2.5.2 通过非透明桥片进行数据传递 - 65 - 2.6 小结 - 67 - 第3章PCI总线的数据交换 - 68 - 3.1 PCI设备BAR空间的初始化 - 68 - 3.1.1 存储器地址与PCI总线地址的转换 - 68 - 3.1.2 PCI设备BAR寄存器和PCI桥Base、Limit寄存器的初始化 - 70 - 3.2 PCI设备的数据传递 - 72 - 3.2.1 PCI设备的正向译码与负向译码 - 72 - 3.3.2 处理器到PCI设备的数据传送 - 74 - 3.2.3 PCI设备的DMA操作 - 74 - 3.2.4 PCI桥的Combining、Merging和Collapsing - 75 - 3.3 与Cache相关的PCI总线事务 - 77 - 3.3.1 Cache一致性的基本概念 - 77 - 3.3.2 PCI设备对不可Cache的存储器空间进行DMA读写 - 82 - 3.3.3 PCI设备对可Cache的存储器空间进行DMA读写 - 83 - 3.3.4 PCI设备进行DMA写时发生Cache命中 - 85 - 3.3.5 DMA写时发生Cache命中的优化 - 87 - 3.4 预读机制 - 89 - 3.4.1 Instruction Fetch - 89 - 3.4.2 数据预读 - 92 - 3.4.3 软件预读 - 94 - 3.4.4 硬件预读 - 96 - 3.4.5 PCI总线的预读机制 - 97 - 3.5 小结 - 101 - 第II篇PCI Express体系结构概述 - 102 - 第4章PCIe总线概述 - 104 - 4.1 PCIe总线的基础知识 - 104 - 4.1.1 端到端的数据传递 - 104 - 4.1.2 PCIe总线使用的信号 - 106 - 4.1.3 PCIe总线的层次结构 - 109 - 4.1.4 PCIe链路的扩展 - 111 - 4.2 PCIe体系结构的组成部件 - 114 - 4.2.1 基于PCIe架构的处理器系统 - 114 - 4.2.2 RC的组成结构 - 117 - 4.2.3 Switch - 118 - 4.2.4 VC和端口仲裁 - 120 - 4.2.5 PCIe-to-PCI/PCI-X桥片 - 122 - 4.3 PCIe设备的扩展配置空间 - 124 - 4.4 小结 - 125 - 第5章PCIe总线的事务层 - 126 - 5.1 TLP的格式 - 126 - 5.1.1 通用TLP头的Fmt字段和Type字段 - 127 - 5.1.2 TC字段 - 129 - 5.1.3 Attr字段 - 130 - 5.1.4 通用TLP头中的其他字段 - 131 - 5.2 TLP的路由 - 133 - 5.2.1 基于地址的路由 - 133 - 5.2.2 基于ID的路由 - 135 - 5.2.3 隐式路由 - 138 - 5.3 存储器、I/O和配置读写请求TLP - 139 - 5.3.1 存储器读写请求TLP - 139 - 5.3.2 完成报文 - 144 - 5.3.3 配置读写请求TLP - 146 - 5.3.4 消息请求报文 - 147 - 5.4 TLP中与数据负载相关的参数 - 148 - 5.4.1 Max_Payload_Size参数 - 148 - 5.4.2 Max_Read_Request_Size参数 - 149 - 5.4.3 RCB参数 - 149 - 5.5 小结 - 150 - 第6章 MSI和MSI-X中断机制 - 151 - 6.1 MSI/MSI-X Capability结构 - 152 - 6.1.1 MSI Capability结构 - 152 - 6.1.2 MSI-X Capability结构 - 154 - 6.2 PowerPC处理器如何处理MSI中断请求 - 158 - 6.2.1 MSI中断机制使用的寄存器 - 159 - 6.2.2 系统软件如何初始化PCIe设备的MSI Capability结构 - 162 - 6.3 x86处理器如何处理MSI-X中断请求 - 165 - 6.3.1 Message Address字段和Message Data字段的格式 - 165 - 6.3.2 FSB Interrupt Message总线事务 - 168 - 6.4 小结 - 169 - 结束语 - 170 -

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值