Linux嵌入式驱动初体验(四)--- 驱动结构分析

      在Linux系统上编写驱动程序,说简单也简单,说难也难。难在于对算法的编写和设备的控制方面,是比较让人头疼的;说它简单是因为在Linux下已经有一套驱动开发的模式,编写的时候只需要按照这个模式写就可以了,而这个模式就是它事先定义好的一些结构体,在驱动编写的时候,只要对这些结构体根据设备的需求进行适当的填充,就实现了驱动的编写。

      首先在Linux下,视一切事物皆为文件,它同样把驱动设备也看成是文件,对于简单的文件操作,无非就是open/close/read/write,在Linux对于文件的操作有一个关键的数据结构:file_operation,它的定义在源码目录下的include/linux/fs.h中,内容如下:

 

对于这个结构体中的元素来说,大家可以看到每个函数名前都有一个“*”,所以它们都是指向函数的指针。目前我们只需要关心

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

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

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

int (*open) (struct inode *, struct file *);

int (*release) (struct inode *, struct file *);

这几条,因为这篇文章就叫简单驱动。就是读(read)、写(write)、控制(ioctl)、打开(open)、卸载(release)。这个结构体在驱动中的作用就是把系统调用和驱动程序关联起来,它本身就是一系列指针的集合,每一个都对应一个系统调用。

      但是毕竟file_operation是针对文件定义的一个结构体,所以在写驱动时,其中有一些元素是用不到的,所以在2.6版本引入了一个针对驱动的结构体框架:platform,它是通过结构体platform_device来描述设备,用platform_driver描述设备驱动,它们都在源代码目录下的include/linux/platform_device.h中定义,内容如下:

对于第一个结构体来说,它的作用就是给一个设备进行登记作用,相当于设备的身份证,要有姓名,身份证号,还有你的住址,当然其他一些东西就直接从旧身份证上copy过来,这就是其中的struct device dev,这是传统设备的一个封装,基本就是copy的意思了。对于第二个结构体,因为Linux源代码都是C语言编写的,对于这里它是利用结构体和函数指针,来实现了C语言中没有的“类”这一种结构,使得驱动模型成为一个面向对象的结构。对于其中的struct device_driver driver,它是描述设备驱动的基本数据结构,它是在源代码目录下的include/linux/device.h中定义的,内容如下:

 

依然全部都是以指针的形式定义的所有元素,对于驱动这一块来说,每一项肯定都是需要一个函数来实现的,如果不把它们集合起来,是很难管理的,而且很容易找不到,而且对于不同的驱动设备,它的每一个功能的函数名必定是不一样的,那么我们在开发的时候,需要用到这些函数的时候,就会很不方便,不可能在使用的时候去查找对应的源代码吧,所以就要进行一个封装,对于函数的封装,在C语言中一个对好的办法就是在结构体中使用指向函数的指针,这种方法其实我们在平时的程序开发中也可以使用,原则就是体现出一个“类”的感觉,就是面向对象的思想。

      在Linux系统中,设备可以大致分为3类:字符设备、块设备和网络设备,而每种设备中又分为不同的子系统,由于具有自身的一些特殊性质,所以有不能归到某个已经存在的子类中,所以可以说是便于管理,也可以说是为了达到同一种定义模式,所以linux系统把这些子系统归为一个新类:misc ,以结构体miscdevice描述,在源代码目录下的include/linux/miscdevice.h中定义,内容如下:

 

对于这些设备,它们都拥有一个共同主设备号10,所以它们是以次设备号来区分的,对于它里面的元素,大应该很眼熟吧,而且还有一个我们更熟悉的list_head的元素,这里也可以应证我之前说的list_head就是一个桥梁的说法了。

      其实对于上面介绍的结构体,里面的元素的作用基本可以见名思意了,所以不用赘述了。其实写一个驱动模块就是填充上述的结构体,根据设备的功能和用途写相应的函数,然后对应到结构体中的指针,然后再写一个入口一个出口(就是模块编程中的init和exit)就可以了,一般情况下入口程序就是在注册platform_device和platform_driver(当然,这样说是针对以platform模式编写驱动程序)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
adc-keys 是一个内核模块,用于支持通过 ADC(模拟数字转换器)读取键盘按键数据的设备驱动程序。其数据结构分析如下: 1. struct adc_keys_platform_data 该结构体为 adc-keys 提供了硬件相关的信息,包括 ADC 通道号、键位数组以及 ADC 转换值的范围等信息。具体定义如下: ``` struct adc_keys_platform_data { int num_adc; // ADC 通道数目 u16 adc_keycodes[MAX_ADC_KEYS]; // ADC 转换值对应的键码 u16 adc_keyvals[MAX_ADC_KEYS]; // ADC 转换值的实际值 int adc_key_nsamples; // ADC 转换值的采样次数 int adc_key_hold_time; // 长按时间 int adc_key_range; // ADC 转换值的范围 int acdbank; // ACDBANK(在某些板卡上才使用) int iface; // IFACE(在某些板卡上才使用) int stby_gpio; // 待机模式时是否需要禁止 ADC 转换 enum overflow_policy overflow_policy; // ADC 转换值超出范围时的处理策略 int allow_suspend; // 是否允许系统进入 suspend 模式 int wakeup_source; // 是否允许该设备作为唤醒源 int sleep_voltage; // 系统进入 SLEEP 状态时的电压 }; ``` 2. struct adc_keys_state 该结构体为 adc-keys 驱动提供了内部状态,用于记录键位状态、长按状态等信息。具体定义如下: ``` struct adc_keys_state { struct input_dev *input; // 与该设备关联的 input_dev struct delayed_work work; // 延迟工作队列,用于判断长按状态 s32 value; // 当前 ADC 转换值 s32 last_value; // 上一个 ADC 转换值 s32 active_idx; // 当前键位的索引 s64 event_time; // 上一个事件的时间戳 int state; // 按键状态(按下、弹起等) bool hold_reported; // 是否已经上报长按事件 bool use_hold; // 是否开启长按功能 }; ``` 3. adc_keys_probe adc-keys 设备驱动的 probe 函数,在设备挂载时会被 kernel 调用。此函数会读取和分析硬件相关的数据,包括通道数目、键码和实际值之间的映射关系等,并用这些信息初始化设备状态等数据结构。 4. adc_keys_read adc-keys 设备驱动的 read 函数,用于从 ADC 通道中读取键码信息。此函数首先进行 ADC 转换,然后通过查找 adc_keycodes 和 adc_keyvals 数组来映射实际值到键码。如果转换值超出了 adc_key_range 的范围,则根据 overflow_policy 进行相应的处理。 5. adc_keys_work 该函数作为延迟工作函数,在事件到来时可以被调用。它用于判断长按状态,如果长按事件未被上报,则在相应的时机上报长按事件并打印相应的信息。 总之,adc-keys 设备驱动通过一系列的数据结构来管理硬件和内部的状态信息,并通过 read 函数读取硬件信息、通过 work 函数处理事件的机制来实现键盘按键数据的采集和上报。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值