回顾——华清字符设备驱动框架210508

回顾——华清字符设备驱动框架

一,设备驱动分类
(一)字符设备驱动
1,所产生数据为字符流,
2,常见字符设备有,lcd,keyboard,iic从设备
3,为什么说产生字符流呢,其实大部分设备所产生的数据都是从寄存器中产生,因此数据传输较快,量也不会太大。
(二)块设备驱动
1,数据以块为单位
2,常见块设备为存储设备,如磁盘,硬盘,u盘,flash,sd卡
3,特点,读取数据较大,速度较慢
(三)网络设备驱动
1,常见网络设备有以太网,网卡

二,字符设备驱动框架
作为字符设备驱动要素:
(一)必须有一个设备号,用在众多的设备驱动中进行区分。
(二)用户必须知道设备驱动对应到设备节点(设备吻文件),Linux把所有的设备都看成文件
(三)对设备操作其实就是对文件操作,应用空间操作open,read,write,close的时候,实际在驱动代码有对应的open,read,write,close
(四)比如/dev/led0,这个称为设备节点。设备节点相当于张三(属一个范围),而设备号相当于其中一个张三的身份证号,具有唯一性。

二,申请设备号
(一)作为驱动必须有一个设备号——向系统申请,其是质为整数。

int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
参数1:主设备号
		设备号(32bit--dev_t)==主设备号(12bit)+次设备号(20bit)
		  主设备号:表示一类设备==camera
		  次设备号:表示一类设备中的某一个,比如前置,后置
		  给定的方式有两种:
		  	1,动态--参数1直接填0
		  	2,静态--指定一个整数,
参数2:描述一个设备信息,可以自定义
		/proc/devices列举出所有的已经注册的设备
参数3:文件操作对象--提供open,read,write,close
返回值:正确返回0,错误返回负数
		  

三,创建设备节点
(一)手动创建–缺点:/dev/目录中的文件都是在内存中,断电后/dev/文件就会消失
mknod /dev/设备名 类型 主设备号 次设备号
比如:
mknod /dev/chr0 c 250 0
(二)自动创建(通过udev/mdev机制)

struct class *class_create(owner, name)//创建一个类
参数1:THIS_MODULE(习惯写法,不必深究)
参数2:字符串名字,自定义
返回一个class指针
//创建一个设备文件
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
参数1:class结构体,class_create调用之后到返回值
参数2:表示父亲,一般直接填NULL
参数3:设备号类型 dev_t
		dev_t devt
			#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
			#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
			#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
参数4:私有数据,一般直接填NULL
参数5和6:表示可变参数,字符串,表示设备节点名字

销毁动作:
void device_destroy(devcls, MKDEV(dev_major, 0))
参数1:class结构体,class_create调用之后到返回值
参数2:设备号类型dev_t
void class_destroy(devcls)
参数1:class结构体,class_create调用之后的返回值
另外,一般原则为先创建的,后销毁

(三)总结
如果说想让整个驱动一开机,一装载驱动的时候就能看到设备节点,那么可以用device_create。在整个操作过程中,如果说想要申请设备号,那么用register_chrdev;如果说,创建设备节点,那就需要用上两个函数,一个叫class_create,一个叫device_create。这两个函数一旦操作成功的时候,就会自动在dev目录下,去创建一个设备节点名字,设备节点名字以device_create这个函数最后的一个参数为准。

四,在驱动中实现文件io的接口,应用程序可以调用文件io
(一)驱动中实现文件io操作接口:struct file_operations

(二)应用程序如何去调用文件io去控制驱动–open,read,write,close
五,应用程序需要传递数据给驱动

站在内核角度看待这两个函数
(一)int copy_to_user(void __user *to, const void *from, unsigned long n)
//将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用
参数1:应用驱动中的一个buffer
参数2:内核空间到一个buffer
参数3:个数
返回值:大于0,表示出错,剩下多少个没有拷贝成功
		等于0,表示正确	
(二)int copy_from_user(void *to, const void __user *from, unsigned long n)
//将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
参数1:内核驱动中的一个buffer
参数2:应用空间到一个buffer
参数3:个数

六,控制外设,其实就是控制地址,内核驱动中是通过虚拟地址操作

void *ioremap(cookie, size)
参数1:物理地址
参数2:长度
返回值:虚拟地址

去映射--解除映射
	void iounmap(void __iomem *addr)
	参数1:映射之后的虚拟地址

七,通过驱动控制led灯
比如,
led-- GPX2_7 —GPX2CON == 0x1100C40
GPX2DAT == 0x1100C44
将0x11000C40映射成虚拟地址
对虚拟地址中的[32:28] = 0x1
八,应用程序和驱动扮演的是什么角色
用户态:应用程序
策略:怎么去做
led:1,一闪一闪
2,10s闪一次…
硬件控制权实质上是在应用程序中(程序员)
------------------------------------------------------------
内核态:驱动
机制:能做什么
led:亮和灭
九,编程规范
(一)面向对象编程思想
用一个结构体来表示一个对象

//***例:用一个结构体来表示一个对象***
***设计一个类型,描述一个设备的信息***/
struct led_desc{
	unsigned int dev_major; //设备号
	struct class *cls;
	struct device *dev; //创建设备文件
	void *reg_virt_base;
};
struct led_desc *led_dev;表示一个全局的设备对象

//实例化全局的设备对象--分配空间
//GFP_KERNEL,如果当前内存不够用的时候该函数会移植阻塞(休眠)
//头文件#include <linux/slab.h>
led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
if(led_dev == NULL)
{
	printk(KERN_ERR "malloc error\n");
	return -ENOMEM;
}

(二)做出错处理
在某个位置出错了,要将之前申请到的资源进行释放

//例:
led_dev = kmalloc(sizeof(struct led_desc), GFP_HERNEL);
led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
if(led_dev->dev_major < 0)
{
	printk(KERN_ERR "register_chrdev error\n");
	ret = -ENODEV;
	goto err_0;
}

err_0:
	kfree(led_dev);
	return ret;

十,编写字符设备驱动的步骤和规范
步骤:
(一)实现模块加载和卸载函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);
(二)在模块加载入口函数中
1,申请主设备号(内核中用于区分和管理不同字符设备)

register_chrdev(dev_major, "chr_dev_test", &my_fops);

2,创建设备节点文件(为用户提供一个可操作的文件接口–open() )

struct class *class_create(THIS_MODULE, "chr_cls");
struct device *device_create(devcls, NULL, MKDEV(dev_major,0), NULL, "chr2");

3,硬件的初始化
a,地址的映射

gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);

b,中断的申请
c,实现硬件寄存器的初始化
//需要配置gpio功能为输出

*gpx2conf &= ~(0xf<<28);
*gpx2conf |= (0x1<<28);

e,实现file_operations

十一,操作寄存器地址的方式:

(一)
volatile unsigned long *gpxcon;
*gpxcon &= ~(0xf<<28);

(二)readl/writel();

u32 read;(const volatile void __iomem *addr) //从地址中读取地址空间的值
void writel(unsigned long value, const volatile void __ *addr) //将value的值写入到addr地址
例子:
//gpio的输出功能的配置
u32 value = readl(led_dev->reg_virt_base);
value &= ~(0xf<<28);
value |= (0x1<<28);
writel(value, led_dev->reg_virt_base);
或者:
*gpx2dat |= (1<<7);
替换成:
writel(read;(led_dev->reg_virt_base + 4) | (1<<7), led_dev->reg_virt_base + 4)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值