Android Kernel 开发系列培训
主讲人:吴庆棋
E-Mail:
[email protected]
Linux开发环境篇
Linux驱动开发篇
Linux内核篇
一。Android Kernel 开发环境搭建
kernelversion :linux 2.6.25
GCC编译器 :toolchain/arm-eabi-4.2.1
linux 主机环境:ubuntu-8.10
1.编译器安装
kernel开发源码包中有toolchain目录,此目录为armgcc交叉编译器
2.配置及编译kernel
<1>编辑Makefile 指定编译器路径
$sudovimMakefile
<2>使用如下命令来配置内核
$sudo make menuconfig
注意:
运行过程中若出现如下错误 :
HOSTCC scripts/basic/fixdep
HOSTCC scripts/basic/docproc
HOSTCC scripts/basic/hash
HOSTCC scripts/kconfig/conf.o
scripts/kconfig/conf.c: 在函数‘conf_askvalue’中:
scripts/kconfig/conf.c:104: 警告: 忽略声明有warn_unused_result 属性的‘fgets’的返回值
scripts/kconfig/conf.c: 在函数‘conf_choice’中:
scripts/kconfig/conf.c:306: 警告: 忽略声明有warn_unused_result 属性的‘fgets’的返回值
HOSTCC scripts/kconfig/kxgettext.o
*** Unable to find the ncurses libraries or the
*** required header files.
*** 'make menuconfig' requires the ncurses libraries.
***
*** Install ncurses (ncurses-devel)and try again.
***
make[1]: *** [scripts/kconfig/dochecklxdialog] 错误1
make: *** [menuconfig] 错误2
请执行如下命令安装ncurses
$ sudo apt-get install libncurses5-dev
安装完成后就可以使用如下命令了:
$make menuconfig /*配置内核*/
$make /*编译内核*/
$make clean /*清除已编译的文件*/
二.Android Kernel 源代码阅读工具介绍
方案一:在window环境下阅读源代码
z linux 下建立SAMBA文件服务器 和SSH服务器
z window下安装source insight代码阅读工具
z window 下安装putty远程终端登入工具
方案二:在linux环境下阅读源代码
z vi文本编辑器
z cscope + ctags
1.搭建window下的阅读代码环境
<1>Ubuntu linux 下建立SAMBA文件服务器
建立不需用户名、密码的Samba共享文件夹
(注:若在window下无法创建和修改文件,在linux下用chmod 777 XXX -R改变权限)
z 安装Samba服务器:
#apt-get install samba
z 创建要共享的文件夹:
#mkdir /root/share
#chmod 777 /root/share -R /*改变文档属性为所有用户可读写*/
z 备份并编辑smb.conf
#cp /etc/samba/smb.conf /etc/samba/smb.conf.bak
#vim /etc/samba/smb.conf
修改[global]的内容如下:
修改workgroup:
Workgroup = SAMBA
增加对中文的支持:
display charset = UTF-8
dos charset = cp936
unix charset = UTF-8
修改security:
Security = user
修改encrypt passwords:
encrypt passwords = yes
增加共享的文件夹:
[Share]
comment = My Share Directory
path=/root/share
available = yes
browseable = yes
public = yes
writable = yes
create mode = 0664
directory mode = 0775
z 测试参数是否正确:
#testparm
z 重启samba服务:
#/etc/init.d/samba restart
z 在window下测试samba服务器
程序->运行 输入linux的IP地址:\\xxx.xxx.xxx.xxx
若可以看到share的文件夹,说明已成功了。
z 将内核源代码拷贝到这个目录,现在可以用source insight 看代码了!!!
<2>Ubuntu linux 安装SSH终端登入服务器
z 请在终端使用命令测试:
ssh localhost
若出现以下错误,则是因为还没有安装ssh-server:
ssh: connect to host localhost port 22: Connection refused
z 安装SSH-server:
$sudo apt-get install openssh-server
z 启动SSH-Server
sudo /etc/init.d/ssh start
现在可以在window下用终端工具登入linux机器进行make 编译。
(建议使用:Putty、secureCRT)
2.搭建linux下的阅读代码环境
<1>安装文本编辑器:$sudo apt-get install vim
<2>制作cscope索引文件:$sudo apt-get install cscope
<3>制作ctags文件: $sudo apt-get install ctags
使用方法:
a) 首先进入kernel的主目录(也就是内核源代码根目录)
b) 输入: make cscope
c) 输入: ctags -R
然后就可以用vi来阅读源码了.
注意:要记住,不要再改变你的当前工作目录了.
比如你要查看init/main.c,你要用: vi init/main.c
而不要 cd init; vi main.c
跟踪函数使用: Ctrl+] (同时按下Ctrl键和"]"键)
如果此函数有多个实例,会有个列表供你选择.
返回上一级函数使用: Ctrl+t (同时按下Ctrl键和"t"键)
在vim中执行”:help tags”命令查询它的用法
二.Android linux 驱动开发
1.Android linux 驱动开发术语
z 内核模块与应用程序
一个应用程序从头到尾完成一个任务,而模块则是为以后处理某些请求而注册自
己,完成这个任 务后,它的“主”函数就立即中止了。
z 内核空间和用户空间
当谈到软件时,我们通常称执行态为“内核空间”和“用户空间”,在Linux系统中,
内核在最高级执行,也称为“管理员态”,在这一级任何操作都可以执行。 而应用
程序则执行在最低级,所谓的“用户态”,在这一级处理器禁止对硬件的直 接访问
和对内存的未授权访问。
模块是在所谓的“内核空间”中运行的,而应用程 序则是在“用户空间”中运行的。
它们分别引用不同的内存映射,也就是程序代码 使用不同的“地址空间”。
Linux通过系统调用和硬件中断完成从用户空间到内核空间的控制转移。执行系统
调用的内核代码在进程的上下文中执行,它执行调用进程的操作而且可以访问进程
地址空间的数据。但处理中断与此不同,处理中断的代码相对进程而言是异步的,
而且与任何一个进程都无关。模块的作用就是扩展内核的功能,是运行在内核空间
的模块化的代码。模块的某些函数作为系统调用执行,而某些函数则负责处理中
断。
2.Android linux 设备驱动开发的基本流程
由于嵌入式设备由于硬件种类非常丰富,在默认的内核发布版中不一定包括所有驱动程
序。所以进行嵌入式Linux系统的开发,很大的工作量是为各种设备编 写驱动程序。除非
系统不使用操作系统,程序直接操纵硬件。Android Linux系统驱动程序开发与普通Linux
开发没有区别。可以在硬件生产厂家或者Internet 上 寻找驱动程序,也可以根据相近的硬
件驱动程序来改写,这样可以加快开发速度。 实现一个嵌入式Linux设备驱动的大致流程
如下。
(1)查看原理图,理解设备的工作原理。一般嵌入式处理器的生产商提供参考电路,也可
以根据需要自行设计。
(2)实现初始化函数。在驱动程序中实现驱动的注册和卸载。
(3)实现中断服务,并用request_irq向内核注册,中断并不是每个设备驱动所必需的。
(4)实现定时器扫描服务,扫描方式也不是每个设备驱动所必需的。
(5)编译该驱动程序到内核中,或者用insmod命令加载模块。
(6)测试该设备,编写应用程序,对驱动程序进行测试。
3.Android linux 设备驱动开发关键函数介绍
(1)设备驱动初始化与注册函数
从Linux 2.6起引入了一套新的驱动管理和注册机制:Platform_device和Platform_driver。Linux中
大部分的设备驱动,都可以使用这套机制, 设备用Platform_device表示,驱动用Platform_driver
进行注册。
Linux platform driver机制和传统的device driver 机制(通过driver_register函数进行注册)相比,一
个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动
程序中使用这些资源时通过platform device提供的标准接口进行申请并使用。这样提高了驱动
和资源管理的独立性,并且拥有较好的可移植性和安全性(这些标准接口是安全的)。
int platform_device_register(struct platform_device *pdev)
void platform_device_unregister(struct platform_device *pdev)
int platform_driver_register(struct platform_driver *drv)
void platform_driver_unregister(struct platform_driver *drv)
(2)加载和卸载驱动程序
„ 驱动入口函数
在编写模块程序时,必须提供两个函数,
• 一个是int init_module(),
在加载此模块的时候自动调用,负责进行设备驱动程序的初始化工作。
init_module()返回0,表示初始化成功,返回负数表示失败,它在内核中注册一定的功能函
数。在注册 之后,如果有程序访问内核模块的某个功能,内核将查表获得该功能的位置,
然后调 用功能函数。
init_module()的任务就是为以后调用模块的函数做准备。
• 另一个函数是void cleanup_module(),
该函数在模块被卸载时调用,负责进行 设备驱动程序的清除工作。
这个函数的功能是取消init_module()所做的事情,把init_module()函数在内核中注册的功能
函数完全卸载,如果没有完全卸载,在此模块下次调用时,将会因为有重名的函数而导致
调入失败。
函数原型:
#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn) \
static inline exitcall_t __exittest(void) \
{ return exitfn; } \
void cleanup_module(void) __attribute__((alias(#exitfn)));
„ 模块加载与卸载
虽然模块作为内核的一部分,但并未被编译到内核中,它们被分别编译和链接 成目标文
件。
Linux中模块可以用C语言编写,用gcc命令编译成模块*.o,
在命令行里加上-c的参数和“-D__KERNEL__ -DMODULE”参数。
然后用depmod -a 使此模块成为可加载模块。
模块用insmod 命令加载,用rmmod 命令来卸载,这两个命令分别调用init_module()和
cleanup_ module()函数,还可以用lsmod命 令来查看所有已加载的模块的状态。
insmod 命令可将编译好的模块调入内存。内核模块与系统中其他程序一样是已链接的目标
文件,但不同的是它们被链接成可重定位映像。insmod 将执行一个 特权级系统调用
get_kernel_sysms()函数以找到内核的输出内容,insmod修改模块对内核符号的引用后,将
再次使用特权级系统调用create_module()函数来申请足够的物理内存空间,以保存新的模
块。内核将为其分配一个新的module结 构,以及足够的内核内存,并将新模块添加在内
核模块链表的尾部,然后将新模块标记为uninitialized。
利用rmmod命令可以卸载模块。如果内核中还在使用此模块,这个模块就不能被卸载。原
因是如果设备文件正被一个进程打开就卸载还在使用的内核模块,并导致对内核模块的读/
写函数所在内存区域的调用。如果幸运,没有其他代码被加载到那个内存区域,将得到一
个错误提示;否则,另一个内核模块被加载到同一区域,这就意味着程序跳到内核中另一
个函数的中间,结果是不可预见的。
(3)内存操作函数
作为系统核心的一部分,设备驱动程序在申请和释放内存时不是调用malloc和free,而代
之以调用kmalloc和kfree,它们在linux/kernel.h 中被定义为:
void * kmalloc(unsigned int len, int priority);
void kfree(void * obj);
参数len 为希望申请的字节数,obj为要释放的内存指针。priority为分配内 存操作的优先
级,即在没有足够空闲内存时如何操作,一般由取值GFP_KERNEL解决即可。
(4)时钟函数
在设备驱动程序中,一般都需要用到计时机制。在Linux系统中,时钟是由系统接管的,设
备驱动程序可以向系统申请时钟。与时钟有关的系统调用有:
#include
#include
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
inline void init_timer(struct timer_list * timer);
struct timer_list的定义为:
struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long d);
};
其中,expires是要执行function 的时间。系统核心有一个全局变量jiffies 表示当 前时间,
一般在调用add_timer时jiffies=JIFFIES+num,表示在num个系统最小时间间 隔后执行
function函数。系统最小时间间隔与所用的硬件平台有关,在核心里定义了常 数HZ表示一
秒内最小时间间隔的数目,则num*HZ表示num秒。系统计时到预定时间就调 用
function,并把此子程序从定时队列里删除,可见,如果想要每隔一定时间间隔执行 一次
的话,就必须在function里再一次调用add_timer。Function的参数d即为timer里面的data
项。
在linux 2.6版本以上的内核中新增了高精度定时器的支持。有关的系统调用有:
#include
#include
#include
void hrtimer_init(struct hrtimer *timer, clockid_t which_clock, enum hrtimer_mode mode);
/* Basic timer operations: */
int hrtimer_start(struct hrtimer *timer, ktime_t tim, const enum hrtimer_mode mode);
int hrtimer_cancel(struct hrtimer *timer);
static inline ktime_t ktime_add(const ktime_t add1, const ktime_t add2)
static inline int hrtimer_restart(struct hrtimer *timer)
struct hrtimer { 定义为
struct rb_node node;
ktime_t _expires;
ktime_t _softexpires;
enum hrtimer_restart (*function)(struct hrtimer *);
struct hrtimer_clock_base *base;
unsigned long state;
struct list_head cb_entry;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
};
其中,ktime_t tim 是要执行function的时间。系统计时到预定时间就调 用function,并把此
子程序从定时队列里删除,可见,如果想要每隔一定时间间隔执行 一次的话,就必须在
function里再一次调用 hrtimer_restart或是返回宏HRTIMER_RESTART。
(5)中断管理
设备驱动程序通过调用request_irq函数来申请中断,通过free_irq来释放中断。
int request_irq(
unsigned int irq,
void (*handler)(int irq,void dev_id,struct pt_regs *regs),
unsigned long flags,
const char *device,
void *dev_id
);
void free_irq(unsigned int irq, void *dev_id);
通常从request_irq函数返回的值为0时,表示申请成功;负值表示出现错误。
y irq 表示所要申请的硬件中断号。
y handler为向系统登记的中断处理子程序,中断产生时由系统来调用,调用时所 带参
数irq为中断号,dev_id为申请时告诉系统的设备标识,regs为中断发生时寄存器 内容。
y device为设备名,将会出现在/proc/interrupts文件里。
y flag 是申请时的选项,它决定中断处理程序的一些特性,其中最重要的是决定中 断处
理程序是快速处理程序(flag 里设置了SA_INTERRUPT)还是慢速处理程序(不设置
SA_INTERRUPT)。
下面的代码将在SBC-2410X的Linux中注册外部中断2。
eint_irq = IRQ_EINT2;
set_external_irq (eint_irq, EXT_FALLING_EDGE,GPIO_PULLUP_DIS);
ret_val = request_irq(eint_irq,eint2_handler, “S3C2410X eint2”,0);
if(ret_val < 0)
return ret_val;
}
4.如何向内核增加一个新的驱动
对于一个开发者来说,将自己开发的内核代码添加到Linux 内核中,需要有三个步骤。
(1)确定把自己的开发代码放入到内核的位置;
(2)把自己开发的功能增加到Linux 内核的配置选项中,使用户能够选择此功能
(3)构建子目录Makefile,根据用户的选择,将相应的代码编译Linux 内核中。
下面,我们就通过一个简单的例子,结合前面学到的知识,来说明如何将前面设计的驱动
加入到Linux 内核。
z 将在driver/input/keyboard目录中添加一个按键驱动testkbd.c
z 修改driver/input/keyboard/Makefile文件
z 修改driver/input/keyboard/Kconfig文件
z 修改完成后,用make menuconfig 查看键盘菜单中是否有新增了一项驱动(如下图)
5.AD按键驱动实例(T28 MID 按键)
图2-4-1 Ad按键电路图
(1)Ad按键接口设计
RK2806提供了4个输入通道的AD控制器,可以方便地输入各种信号。T28目标板选用
RK2806微处理器,带有5个接到AD通道1的按键, 硬件原理图如图2-4-1所示。按键控
制采用AD采样方式 ,利用采样出来的AD值的不同分别区别不同的按键按下。与按键相
连的通用AD控制器由表6.3所示的控制寄存器配置
图2-4-2 AD控制器寄存器列表
图2-4-3 AD 控制寄存器
(2)驱动源代码解析
z Ad控制器驱动接口函数
#define IN_API_DRIVER_ADC
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*----------------------------------------------------------------------
Name : ADCReadData(void)
Desc :读ADC的值
Params : 无
Return : 还回ADC的值
----------------------------------------------------------------------*/
int32 ADCReadData(void)
{
pADC_REG pheadAdc;
int32 adcReturn;
pheadAdc = (pADC_REG) ADC_BASE_ADDR_VA;
if (pheadAdc->ADC_STAS == ADC_STOP)
{
adcReturn = (pheadAdc->ADC_DATA & 0x3ff);
}
else
{
adcReturn= -1;
}
return (adcReturn);
}
/*----------------------------------------------------------------------
Name : ADCStart(uint8 ch)
Desc :启动ADC的转换 ch (0---3)
Params : 无
Return :启动成功还回为0,否则为 -1
----------------------------------------------------------------------*/
int32 ADCStart(uint8 ch)
{
pADC_REG pheadAdc;
pheadAdc = (pADC_REG) ADC_BASE_ADDR_VA;
//rockchip_scu_reset_unit( 7 );
if(ch>3)
{
return (-1);
}
pheadAdc->ADC_CTRL = (pheadAdc->ADC_CTRL & 0xe0) | ADC_POWER_ON |
ADC_START |ch;
return (0);
}
/*----------------------------------------------------------------------
Name : ADCIntEnabled()
Desc :打开ADC中断使能
Params : 无
Return :
----------------------------------------------------------------------*/
void ADCIntEnabled(void)
{
pADC_REG pheadAdc;
pheadAdc = (pADC_REG) ADC_BASE_ADDR_VA;
pheadAdc->ADC_CTRL = (pheadAdc->ADC_CTRL & 0xdf) | ADC_ENABLED_INT;
}
/*----------------------------------------------------------------------
Name : ADCIntDisabled(void)
Desc :关闭ADC中断使能
Params : 无
Return :
----------------------------------------------------------------------*/
void ADCIntDisabled(void)
{
pADC_REG pheadAdc;
pheadAdc = (pADC_REG) ADC_BASE_ADDR_VA;
pheadAdc->ADC_CTRL = (pheadAdc->ADC_CTRL & 0xdf) | ADC_DISABLED_INT;
}
/*----------------------------------------------------------------------
Name : ADCIntHander(void)
Desc :ADC中断服务程序
Params : 无
Return :
----------------------------------------------------------------------*/
void ADCIntHander(void)
{
int32 adcData;
ADCIntDisabled();
adcData = ADCReadData();
}
/*----------------------------------------------------------------------
Name :ADCInit(void)
Desc :ADC初值化
Params : 无
Return :初始化成功还回为0,否则为 -1
----------------------------------------------------------------------*/
int32 ADCInit(void)
{
pADC_REG pheadAdc;
pheadAdc = (pADC_REG) ADC_BASE_ADDR_VA;
rockchip_scu_register( SCU_IPID_LSADC , SCU_MODE_FREQ , 1 , NULL ); /* max 1M
CLK*/
pheadAdc->ADC_CTRL = ADC_POWER_ON;
if(ADCStart(Adc_channel0) !=0)
{
return (-1);
}
return (0);
}
/*----------------------------------------------------------------------
Name : ADCDeinit(void)
Desc :ADC反初值化
Params : 无
Return :
----------------------------------------------------------------------*/
void ADCDeinit(void)
{
pADC_REG pheadAdc;
pheadAdc = (pADC_REG) ADC_BASE_ADDR_VA;
pheadAdc->ADC_CTRL = ADC_POWER_OFF;
rockchip_scu_disableclk( SCU_IPID_LSADC );
}
/*----------------------------------------------------------------------
Name : RockAdcScanning(void)
Desc :系统adc 扫描,对四个通道的adc定时扫描
Params : 无
Return :
----------------------------------------------------------------------*/
void RockAdcScanning(void)
{
int32 adcTemp;
adcTemp = ADCReadData();
if (adcTemp != -1)
{
g_adcValue[g_adcch] = (uint16)adcTemp;
}
else
{
printk ("\nRockAdcScanning: not stop");
return;
}
g_adcch++;
if (g_adcch>=Adc_channel_max)
g_adcch = Adc_channel0;
ADCStart(g_adcch);
}
/*BATTERY ADC*/
u16 get_rock_adc0(void)
{
return g_adcValue[0];
}
int get_rock_adc1(void)
{
return g_adcValue[1];
}
int get_rock_adc2(void)
{
return g_adcValue[2];
}
/*BATTERY VREF_ADC*/
u16 get_rock_adc3(void)
{
return g_adcValue[3];
}
z 按键驱动
/*设备资源*/
static struct resource key_resources[] = {
[0] = {
.start = ADC_BASE_ADDR,
.end = ADC_BASE_ADDR + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = RK28_ID_ADC,
.end = RK28_ID_ADC,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device rk28_key_device = {
.name = "rk28_AD_button",
.id = -1,
.resource = key_resources,
.num_resources = ARRAY_SIZE(key_resources),
};
void rock28_add_device_key(void)
{
platform_device_register(&rk28_key_device);/*设备资源注册*/
}
1.系统资源和宏定义
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* Keypad Controller registers
*/
//#define RK28_PRINT
#include 0
#include
/* Debug */
#if 0
#define DBG(x...) printk(KERN_INFO x)
#else
#define DBG(x...)
#endif
//ROCKCHIP AD KEY CODE ,for demo board
// key ---> EV
#if 1
#define AD1KEY1 103//DPAD_UP ----------UP
#define AD1KEY2 106//DPAD_RIGHT-------FFD
#define AD1KEY3 105//DPAD_LEFT--------FFW
#define AD1KEY4 108//DPAD_DOWN------DOWN
#define AD1KEY5 62 //ENDCALL WAKE_DROPPED
#define AD1KEY6 28 //ENTER
#define AD2KEY1 59 //MENU //115 //VOLUME_UP
#define AD2KEY2 102 //HOME //114 //VOLUME_DOWN
#define AD2KEY3 158 //BACK----ESC //59 //MENU
#define AD2KEY4 114 //VOLUME_DOWN //62 //ENDCALL
#define AD2KEY5 115 //VOLUME_UP //158 //BACK------------ESC
#define AD2KEY6 116 //POWER
#else
#define AD1KEY1 103//DPAD_UP ----------UP
#define AD1KEY2 106//DPAD_RIGHT-------FFD
#define AD1KEY3 105//DPAD_LEFT--------FFW
#define AD1KEY4 108//DPAD_DOWN------DOWN
#define AD1KEY5 28 //ENTER
#define AD1KEY6 28 //ENTER
#define AD2KEY1 103//DPAD_UP ----------UP
#define AD2KEY2 106//DPAD_RIGHT-------FFD
#define AD2KEY3 108//DPAD_DOWN------DOWN
#define AD2KEY4 59 //MENU
#define AD2KEY5 158 //BACK------------ESC
#define AD2KEY6 116 //POWER
#endif
#define Valuedrift 70
#define EmptyADValue 950 //1000
#define InvalidADValue 10
#define ADKEYNum 12
/*power event button*/
#define POWER 116
#define ENDCALL 62
#define ONESEC_TIMES 100
#define SEC_NUM 1
#define SLEEP_TIME 2 /*per 40ms*/
static unsigned int pwrscantimes = 0;
static unsigned int valuecount = 0;
static unsigned int g_code = 0;
static unsigned int g_wake =0;
extern int rk28_pm_status ;
#define KEY_PHYS_NAME "rk28_AD_button/input0"
//ADC Registers
typedef struct tagADC_keyst
{
unsigned int adc_value;
unsigned int adc_keycode;
}ADC_keyst,*pADC_keyst;
// adc ---> key
static ADC_keyst ad1valuetab[] = {
{ 95, AD1KEY1},
{247,AD1KEY2},
{403,AD1KEY3},
{561,AD1KEY4},
{723,AD1KEY5},
{899,AD1KEY6},
{EmptyADValue,0}
};
static ADC_keyst ad2valuetab[] = {
{95, AD2KEY1},
{249, AD2KEY2},
{406, AD2KEY3},
{561, AD2KEY4},
{726, AD2KEY5},
{899, AD2KEY6},
{EmptyADValue,0}
};
//key code tab
static unsigned char initkey_code[ ] =
{
AD1KEY1, AD1KEY2,AD1KEY3,AD1KEY4,AD1KEY5,AD1KEY6,
AD2KEY1,
AD2KEY2,AD2KEY3,AD2KEY4,AD2KEY5,AD2KEY6,ENDCALL,KEY_WAKEUP
};
struct rk28_AD_button_platform_data {
int x;
};
struct rk28_AD_button {
struct rk28_AD_button_platform_data *pdata;
struct timer_list timer;
struct clk *clk;
struct input_dev *input_dev;
void __iomem *mmio_base;
/* matrix key code map */
unsigned char keycodes[13];
/* state row bits of each column scan */
uint32_t direct_key_state;
unsigned int direct_key_mask;
int rotary_rel_code[2];
int rotary_up_key[2];
int rotary_down_key[2];
};
struct rk28_AD_button *prockAD_button;
extern void RockAdcScanning(void);
extern void printADCValue(void);
extern int32 ADCInit(void);
extern int get_rock_adc1(void);
extern int get_rock_adc2(void);
unsigned int find_rock_adkeycode(unsigned int advalue,pADC_keyst ptab)
{
while(ptab->adc_value!=EmptyADValue)
{
if((advalue>ptab->adc_value-Valuedrift)&&(advalueadc_value+Valuedrift))
return ptab->adc_keycode;
ptab++;
}
return 0;
}
static int rk28_AD_button_open(struct input_dev *dev)
{
// struct rk28_AD_button *AD_button = input_get_drvdata(dev);
return 0;
}
static void rk28_AD_button_close(struct input_dev *dev)
{
// struct rk28_AD_button *AD_button = input_get_drvdata(dev);
}
#define res_size(res) ((res)->end - (res)->start + 1)
int keydata=58;
static int ADSampleTimes = 0;
/*定时器扫描函数*/
static void rk28_adkeyscan_timer(unsigned long data)
{
unsigned int ADKEY1,code = 0;
/*Enable AD controller to sample */
prockAD_button->timer.expires = jiffies+msecs_to_jiffies(10);
add_timer(&prockAD_button->timer);
RockAdcScanning();
if (ADSampleTimes < 4)
{
ADSampleTimes ++;
goto scan_io_key; /* scan gpio button event*/
}
ADSampleTimes = 0;
/*Get button value*/
ADKEY1=get_rock_adc2();
if((ADKEY1>EmptyADValue)&&(ADKEY1<=InvalidADValue))
goto scan_io_key1;
valuecount++;
if(valuecount < 2)
goto scan_code;
code=find_rock_adkeycode(ADKEY1,ad2valuetab);
valuecount = 2;
goto scan_code; ///scan_code;
scan_io_key1:
valuecount = 0;
scan_code:
if((g_code == 0) && (code == 0)){
goto scan_io_key; }
DBG("\n key button PE2 == %d \n",GPIOGetPinLevel(GPIOPortE_Pin2));
if(code != 0){
if(valuecount<2)
goto scan_io_key;
if(g_code == 0){
g_code = code;
DBG("\n %s::%d rock adc1 key scan ,find press down a key=%d
\n",__func__,__LINE__,g_code);
input_report_key(prockAD_button->input_dev,g_code,1);
input_sync(prockAD_button->input_dev);
goto scan_io_key;
}else{
if(g_code != code){
DBG("\n %s::%d rock adc1 key scan ,find press up a key=%d
\n",__func__,__LINE__,g_code);
input_report_key(prockAD_button->input_dev,g_code,0);
input_sync(prockAD_button->input_dev);
DBG("\n %s::%d rock adc1 key scan ,find press down a key=%d
\n",__func__,__LINE__,code);
input_report_key(prockAD_button->input_dev,code,1);
input_sync(prockAD_button->input_dev);
g_code = code;
goto scan_io_key;
}
}
}
if((g_code != 0)&&(code == 0)&&(ADSampleTimes == 0)){
DBG("\n %s::%d rock adc1 key scan ,find press up a key=%d
\n",__func__,__LINE__,g_code);
input_report_key(prockAD_button->input_dev,g_code,0); input_sync(prockAD_button->input_dev); valuecount = 0;
g_code = 0;
goto scan_io_key;
}
scan_io_key :
if(!GPIOGetPinLevel(GPIOPortE_Pin2))
{
pwrscantimes += 1;
if(pwrscantimes == (SEC_NUM * ONESEC_TIMES))
{
input_report_key(prockAD_button->input_dev,ENDCALL,1);
input_sync(prockAD_button->input_dev);
printk("the kernel come to power down!!!\n");
}
if(pwrscantimes ==( (SEC_NUM + 1)* ONESEC_TIMES))
{
pwrscantimes = 0;
input_report_key(prockAD_button->input_dev,ENDCALL,0);
input_sync(prockAD_button->input_dev);
printk("the kernel come to power up!!!\n");
}
return ;
}
if( pwrscantimes > SLEEP_TIME)
{
pwrscantimes = 0;
if(rk28_pm_status == 0)
{
if(g_wake == 1) /*already wake up*/
{
g_wake = 0;
return;
}
input_report_key(prockAD_button->input_dev,AD1KEY5,1);
input_sync(prockAD_button->input_dev);
input_report_key(prockAD_button->input_dev,AD1KEY5,0);
input_sync(prockAD_button->input_dev);
}
rk28printk("\n%s^^^^Wake Up ^^^^^!!\n",__FUNCTION__);
}
}
void rk28_send_wakeup_key( void )
{
input_report_key(prockAD_button->input_dev,KEY_WAKEUP,1);
input_sync(prockAD_button->input_dev);
input_report_key(prockAD_button->input_dev,KEY_WAKEUP,0);
input_sync(prockAD_button->input_dev);
}
/*中断产生调用此函数*/
static irqreturn_t rk28_AD_irq_handler(s32 irq, void *dev_id)
{
if( rk28_pm_status == 1)
{
/*用于给上层上报一个按键动作*/
input_report_key(prockAD_button->input_dev,AD1KEY5,1);
/*用来告诉上层,本次的事件已经完成了*/
input_sync(prockAD_button->input_dev);
input_report_key(prockAD_button->input_dev,AD1KEY5,0);
input_sync(prockAD_button->input_dev);
g_wake =1;
}
rk28printk("\n%s^^^^Wake Up^^^^^!!\n",__FUNCTION__);
return IRQ_HANDLED;
}
static int __devinit rk28_AD_button_probe(struct platform_device *pdev)
{
struct rk28_AD_button *AD_button;
struct input_dev *input_dev;
int error,i;
/*内存申请*/
AD_button = kzalloc(sizeof(struct rk28_AD_button), GFP_KERNEL);
/* Create and register the input driver. */
input_dev = input_allocate_device();
if (!input_dev || !AD_button) {
dev_err(&pdev->dev, "failed to allocate input device\n");
error = -ENOMEM;
goto failed1;
}
int ret = request_irq(IRQ_NR_ADC, rk28_AD_irq_handler, 0, "ADC", NULL);
if (ret < 0) {
printk(KERN_CRIT "Can't register IRQ for ADC\n");
return ret;
}
memcpy(AD_button->keycodes, initkey_code, sizeof(AD_button->keycodes));
input_dev->name = pdev->name;
input_dev->open = rk28_AD_button_open;
input_dev->close = rk28_AD_button_close;
input_dev->dev.parent = &pdev->dev;
input_dev->phys = KEY_PHYS_NAME;
input_dev->id.vendor = 0x0001;
input_dev->id.product = 0x0001;
input_dev->id.version = 0x0100;
input_dev->keycode = AD_button->keycodes;
input_dev->keycodesize = sizeof(unsigned char);
input_dev->keycodemax = ARRAY_SIZE(initkey_code);
for (i = 0; i < ARRAY_SIZE(initkey_code); i++)
set_bit(initkey_code[i], input_dev->keybit);
/*
*set_bit(EV_KEY, button_dev.evbit);
*set_bit(BTN_0, button_dev.keybit)
*分别用来设置设备所产生的事件以及上报的按键值。
*Struct iput_dev中有两个成员,一个是evbit.一个是keybit.
*分别用表示设备所支持的动作和按键类型。
*/
clear_bit(0, input_dev->keybit);
AD_button->input_dev = input_dev;
input_set_drvdata(input_dev, AD_button);
input_dev->evbit[0] = BIT_MASK(EV_KEY) ;
platform_set_drvdata(pdev, AD_button);
ADCInit();
prockAD_button=AD_button;
/* Register the input device 用来注册一个input device.*/
error = input_register_device(input_dev);
if (error) {
dev_err(&pdev->dev, "failed to register input device\n");
goto failed2;
}
/*定时器初始化*/
setup_timer(&AD_button->timer, rk28_adkeyscan_timer, (unsigned long)AD_button);
AD_button->timer.expires = jiffies + 3;
/*启用定时器*/
add_timer(&AD_button->timer);
/*注册中断回调函数*/
error = request_gpio_irq(GPIOPortE_Pin2,rk28_AD_irq_handler,GPIOEdgelFalling,NULL);
if(error)
{
printk("unable to request recover key IRQ\n");
goto failed2;
}
return 0;
failed2:
input_unregister_device(AD_button->input_dev);
platform_set_drvdata(pdev, NULL);
failed1:
input_free_device(input_dev);
kfree(AD_button);
return error;
}
static int __devexit rk28_AD_button_remove(struct platform_device *pdev)
{
struct rk28_AD_button *AD_button = platform_get_drvdata(pdev);
input_unregister_device(AD_button->input_dev);
input_free_device(AD_button->input_dev);
kfree(AD_button);
platform_set_drvdata(pdev, NULL);
return 0;
}
static struct platform_driver rk28_AD_button_driver = {
.probe = rk28_AD_button_probe,
.remove = __devexit_p(rk28_AD_button_remove),
.driver = {
.name = "rk28_AD_button",
.owner = THIS_MODULE,
},
};
int __init rk28_AD_button_init(void)
{
/*设备驱动注册*/
return platform_driver_register(&rk28_AD_button_driver);
}
static void __exit rk28_AD_button_exit(void)
{
platform_driver_unregister(&rk28_AD_button_driver);
}
/*模块化*/
module_init(rk28_AD_button_init);
module_exit(rk28_AD_button_exit);
MODULE_DESCRIPTION("rk28 AD button Controller Driver");
MODULE_LICENSE("GPL");
三.Android Kernel 源代码分析
1.Platform Device and Driver
platform是一个虚拟总线,相比PCI,USB,它主要用于描述SOC上的资源。platform所描述的
资源有一个共同点,就是在CPU总线直接取址。platform机制将设备本身的资源注册进内核,
由内核统一管理,在驱动程序中使用这些资源时通过platform device提供的标准接口进行申请
并使用。这样提高了驱动和资源管理的独立性,并且拥有较好的可移植性和安全性(这些标准接
口是安全的)。
Platform机制的本身使用并不复杂,由两部分组成:platform_device和platfrom_driver。
通过Platform机制开发发底层驱动的大致流程为:
定义 platform_device_register注册 platform_device
定义 platform_driver_register注册 platform_driver。
z 确认的就是2.6内核定义的platform总线类型
structbus_type platform_bus_type = {/*drivers/base/platform.c*/
.name = "platform",
.dev_attrs =platform_dev_attrs,
.match =platform_match,
.uevent =platform_uevent,
.pm =PLATFORM_PM_OPS_PTR,
};
z 在2.6内核中platform设备用结构体platform_device来描述设备的资源信息,例如设备
的地址,中断号等。
structplatform_device { /*include/linux/platform_device.h*/
const char *name;
intid;
structdevice dev;
u32 num_resources;
structresource *resource;
};
z 该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息
structresource { /*include/linux/ioport.h*/
resource_size_t start;
/*表示资源的起始物理地址和终止物理地址。
*它们确定了资源的范围,也即是一个闭区间[start,end]。
*/
resource_size_t end;
const charname; /*指向此资源的名称。*/
/*描述此资源属性的标志,属性
*flags是一个unsigned long类型的32位标志值,用以描述资源的属性。
*比如:资源的类型、是否只读、是否可缓存,以及是否已被占用等。
*/
unsigned longflags;
/*指针parent、sibling和child:分别为指向父亲、兄弟和子资源的指针*/
structresource parent,sibling,child;
};
z 内核初始化过程
do_basic_setup()/*init/main.c 调用/driver/base下各子系统初始化函数*/
->driver_init() /*drivers/base/init.c 初始化驱动模型*/
->platform_bus_init() /*drivers/base/platform.c
*初始化/sys/bus/platform_bus目录*/
->...初始化platform_bus(虚拟总线)
(1)设备向内核注册的时候
platform_device_register()
->platform_device_add()
->...内核把设备挂在虚拟的platform_bus下
(2)驱动注册的时候
platform_driver_register()
->driver_register()
->bus_add_driver()
->driver_attach()
->bus_for_each_dev()对每个挂在虚拟的platform_bus的设备
作
__driver_attach()
->driver_probe_device()
->drv->bus->match()==platform_match();
比较strncmp(pdev->name,drv->name,BUS_ID_SIZE),
如果相符就调用 platform_drv_probe()->driver->probe(),
如果probe成功则绑定该设备到该驱动.
init执行/etc/rc.d和启动内核外挂模块
2.linux input subsystem 架构分析
z 主要数据结构
Input Subsystem main data structure
数据结构 用途 定义位置 具体数据结构的分配和初始化
Struct input_dev 驱动层物理Input
设备的基本数据
结构
Input.h 通常在具体的设备驱动中分配
和填充具体的设备结构
Struct Evdev
Struct Mousedev
Struct Keybdev…
Event Handler层
逻辑Input 设备
的数据结构
Evdev.c
Mousedev.c
Keybdev.c
Evdev.c/Mouedev.c …中分配
Struct Input_handlerEvent Handler的
结构
Input.h Event Handler层,定义一个具
体的Event Handler。
Struct Input_handle 用来创建驱动层
Dev和Handler
链表的链表项结
构
Input.h Event Handler层中分配,包含
在Evdev/Mousedev…中。
struct input_dev{ /*driver/input/input.h*/
void *private;
const char *name;
const char *phys;
const char *uniq;
struct input_id id;
unsigned long evbit[NBITS(EV_MAX)];
unsigned long keybit[NBITS(KEY_MAX)];
unsigned long relbit[NBITS(REL_MAX)];
unsigned long absbit[NBITS(ABS_MAX)];
unsigned long mscbit[NBITS(MSC_MAX)];
unsigned long ledbit[NBITS(LED_MAX)];
unsigned long sndbit[NBITS(SND_MAX)];
unsigned long ffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)];
unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;
int (*setkeycode)(struct input_dev *dev, int scancode, int keycode);
int (*getkeycode)(struct input_dev *dev, int scancode, int *keycode);
struct ff_device *ff;
unsigned int repeat_key;
struct timer_list timer;
0int state;
int sync;
int abs[ABS_MAX + 1];
int rep[REP_MAX + 1];
unsigned long key[NBITS(KEY_MAX)];
unsigned long led[NBITS(LED_MAX)];
unsigned long snd[NBITS(SND_MAX)];
unsigned long sw[NBITS(SW_MAX)];
int absmax[ABS_MAX + 1];
int absmin[ABS_MAX + 1];
int absfuzz[ABS_MAX + 1];
int absflat[ABS_MAX + 1];
int (*open)(struct input_dev *dev);
void (*close)(struct input_dev *dev);
int (*flush)(struct input_dev *dev, struct file *file);
int (*event)(struct input_dev *dev, unsigned int type, unsigned int code,
int value);
struct input_handle *grab;
struct mutex mutex;
unsigned int users;
struct device dev;
union {
struct device *dev;
} cdev;
struct list_head h_list; /* 是input_handle 链表的list节点*/
struct list_head node;
};
struct input_handle {
void *private;
int open;
const char *name;
struct input_dev *dev;
struct input_handler *handler;
struct list_head d_node;
struct list_head h_node;
};
struct input_handler {
void *private;
void (*event)(struct input_handle *handle, unsigned int type,
unsigned int code, int value);
int (*connect)(struct input_handler *handler, struct input_dev *dev,
const struct input_device_id *id);
void (*disconnect)(struct input_handle *handle);
void (*start)(struct input_handle *handle);
const struct file_operations *fops;
int minor;
const char *name;
const struct input_device_id *id_table;
const struct input_device_id *blacklist;
struct list_head h_list;
struct list_head node;
};
z 架构模型
input subsystem 用来统一处理数据输入设备,例如键盘,鼠标,游戏杆,触摸屏等等。
输入子系统由输入子系统核心层(Input Core ),驱动层和事件处理层(Event Handler)
三部份组成。从下图我们可以看到,input core 用来协调硬件的input 事件 和 用户层应用
之间的通讯。
一个输入事件,如鼠标移动,键盘按键按下,joystick 的移动等等通过Driver -> InputCore
-> Eventhandler -> userspace 的顺序到达用户空间传给应用程序。
(其中Input Core 即Input Layer 由driver/input/input.c及相关头文件实现。对下
提供了设备驱动的接口,对上提供了Event Handler层的编程接口。)
z 输入子系统架构示意图
z 内部结构框架
(1)在内核中,input_dev 表示一个input设备;
所有的input_dev 用双向链表input_dev_list 连起来,如图所示:
/----------------\
/--------------->| input_dev_list|<------------------\
| \----------------/ |
| |
| |
| |
| |
| input_dev input_dev |
| +------------+ +------------+ |
| | | | | |
| +------------+ +------------+ |
| | | | | |
| +------------+ +------------+ |
| | | | | |
| +------------+ +------------+ |
\--->| node |<---- ...... ----| node |<---/
+------------+ +------------+ | h_list | | h_list | +------------+ +------------+
在调用 int input_register_device(struct input_dev *dev) 的时候,会将新的input_dev 加入到
这个链表中。
(2)input_handler 来表示input设备的interface。
所有的input_handler 用双向链表input_handler_list 连起来, 如图所示:
/--------------------\ /--------------------->| input_handler_list |<--------------------\
| \--------------------/ | |
| |
| input_handler input_handler | | +------------------+ +------------------+ |
| | private | | private | |
| +------------------+ +------------------+ |
| | (*event)() | | (*event)() | |
| +------------------+ +------------------+ |
| | (*connect)() | | (*connect)() | |
| +------------------+ +------------------+ |
| | (*disconnect)() | | (*disconnect)() | |
| +------------------+ +------------------+ |
| | (*start)() | | (*start)() | |
| +------------------+ +------------------+ |
| | fops | | fops | |
| +------------------+ +------------------+ |
| | minor | | minor | |
| +------------------+ +------------------+ |
| | name | | name | |
| +------------------+ +------------------+ |
| | id_table | | id_table | |
| +------------------+ +------------------+ |
| | blacklist | | blacklist | |
| +------------------+ +------------------+ |
| | hlist | | hlist | |
| +------------------+ +------------------+ |
\--->| node |<--- ...... ---->| node |<---/
+------------------+ +------------------+
所有的input_handler 用双向链表input_handler_list 连起来, 在调用int
input_register_handler(struct input_handler *handler) 的时候,会将新的input_handler 加入到
这个链表中。
每个input_dev 和input_handler 是要关联上才能工作的,
在注册input_dev 或者input_handler的时候,就遍历上面的列表,找到相匹配的,
然后调用input_handler 的connect函数来将它们联系到一起。
(3)input_handler 的connect函数 和input_handle函数
通常在input_handler 的connect函数中,就会创建input_handle,
input_handle就是负责将input_dev 和input_handler联系在一起的,如图所示:
/----------------\ /--------------->| input_dev_list |<------------------\
| \----------------/ |
| |
| |
| |
| |
/-----------> input_dev input_dev |
| | +------------+ +------------+ |
| | | | | | |
| | +------------+ +------------+ |
| | | | | | |
| | +------------+ +------------+ |
| | | | | | |
| | +------------+ +------------+ |
| \--->| node |<---- ...... ----| node |<---/
| +------------+ +------------+
| /----->| h_list |<-------\ | h_list |
| | +------------+ | +------------+
| | |
| | |
| | |
| | |
| | input_handle |
| | +--------------+ |
| | | private | |
| | +--------------+ |
| | | open | |
| | +--------------+ |
| | | name | |
| | +--------------+ |
| \----->| d_node |<-----/
| +--------------+
\------<-----| *dev |
+--------------+
/-----<--------| *handler |
| +--------------+
| /-----| h_node |<-----\
| | +--------------+ |
| | |
| | |
\------------> input_handler |
| +------------------+ | | | private | | | +------------------+ | | | (*event)() | | | +------------------+ | | | (*connect)() | | | +------------------+ |
| | (*disconnect)() | | | +------------------+ | | | (*start)() | | | +------------------+ | | | fops | | | +------------------+ | | | minor | | | +------------------+ | | | name | | | +------------------+ | | | id_table | | | +------------------+ | | | blacklist | | | +------------------+ | \-->| hlist |<---/ +------------------+
| node |
+------------------+
这里需要额外说明一下的是:input_dev 中的h_node 是input_handle 链表的list 节点,
也就是说,一个input_dev,可以对应多个input_handle.
当设备产生input event 的时候,例如按下了一个键,驱动就会调用input_handler 中的
event 函数,同时,如果input_dev 支持的话,也会调用input_dev 的event 函数。
这样,设备产生的事件就会被驱动记录下来。
当用户层的程序需要获知这些事件的时候,会调用input_handler中的struct file_operations
*fops 中的相应函数,例如read 等等。
可以看出,整个input 框架的构思还是比较简洁方便的。
z 输入子系统源代码解析
输入链路的创建过程: 由于input 子系统通过分层将一个输入设备的输入过程分隔为独立
的两部份:驱动到Input Core,Input Core到Event Handler。所以整个链路的这两部分的接
口的创建是独立的。
(1)硬件设备的注册
驱动层任务:负责和底层的硬件设备打交道,将底层硬件对用户输入的响应转换为标准的
输入事件以后再向上发送给Input Core。
驱动层通过调用Input_register_device函数和Input_unregister_device函数来向输入子系统中
注册和注销输入设备。
这两个函数调用的参数是一个Input_dev结构,这个结构在driver/input/input.h中定义。驱动
层在调用Input_register_device之前需要填充该结构中的部分字段.
set_bit(EV_KEY, input_dev.evbit);
set_bit(KEY_B, input_dev.keybit);
set_bit(KEY_A, input_dev.keybit);
*分别用来设置设备所产生的事件以及上报的按键值。
*Struct iput_dev中有两个成员,一个是evbit.一个是keybit.
*分别用表示设备所支持的动作和按键类型。
evbit字段用来定义该输入设备可以支持的(产生和响应)的事件的类型。
包括:
Ø EV_RST 0x00 Reset
Ø EV_KEY 0x01 按键
Ø EV_REL 0x02 相对坐标
Ø EV_ABS 0x03 绝对坐标
Ø EV_MSC 0x04 其它
Ø EV_LED 0x11 LED
Ø EV_SND 0x12 声音
Ø EV_REP 0x14 Repeat
Ø EV_FF 0x15 力反馈
一个设备可以支持一个或多个事件类型。每个事件类型下面还需要设置具体的触发事件,
比如EV_KEY事件,支持哪些按键等。
(2)Event Handler层
z 注册Input Handler
驱动层只是把输入设备注册到输入子系统中,在驱动层的代码中本身并不创建设备结
点。
应用程序用来与设备打交道的设备结点的创建由Event Handler层调用Input core中
的函数来实现。而在创建具体的设备节点之前,Event Handler层需要先注册一类设备的
输入事件处理函数及相关接口
以MouseDev Handler为例:/*drivers/input/mousedev.c*/
static struct input_handlermousedev_handler = {
.event = mousedev_event,
.connect = mousedev_connect,
.disconnect = mousedev_disconnect,
.fops = &mousedev_fops,
.minor = MOUSEDEV_MINOR_BASE,
.name = "mousedev",
.id_table = mousedev_ids,
};
static int __init mousedev_init(void)
{
/*调用input.c中定义的input_register_handler来注册一个鼠标类型的Handler. 这里
的Handler不是具体的用户可以操作的设备,而是鼠标类设备的统一的处理函数接口。*/
int error;
mousedev_mix = mousedev_create(NULL, &mousedev_handler, MOUSEDEV_MIX);
if (IS_ERR(mousedev_mix))
return PTR_ERR(mousedev_mix);
error = input_register_handler(&mousedev_handler);
if (error) {
mousedev_destroy(mousedev_mix);
return error;
}
#ifdef CONFIG_INPUT_MOUSEDEV_PSAUX
error = misc_register(&psaux_mouse);
if (error)
printk(KERN_WARNING "mice: could not register psaux device, "
"error: %d\n", error);
else
psaux_registered = 1;
#endif
printk(KERN_INFO "mice: PS/2 mouse device common for all mice\n");
return 0;
}
z 注册input device
注册input device的过程就是为input device设置默认值,并将其挂以input_dev_list.与挂载
在input_handler_list中的handler相匹配。如果匹配成功,就会调用handler的connect函数.
connect 函数中,就会创建input_handle, 而input_handle 就是负责将input_dev 和
input_handler联系在一起。
int input_register_device(struct input_dev *dev)
{
static atomic_t input_no = ATOMIC_INIT(0);
struct input_handler *handler;
const char *path;
int error;
在 前面的分析中曾分析过。Input_device 的evbit 表示该设备所支持的事件。在这里将其
EV_SYN 置位,即所有设备都支持这个事件. 如果dev->rep[REP_DELAY]和
dev->rep[REP_PERIOD]没有设值,则将其赋默认值。这主要是处理重复按键的.
__set_bit(EV_SYN, dev->evbit);
/*
* If delay and period are pre-set by the driver, then autorepeating
* is handled by the driver itself and we don't do it in input.c.
*/
init_timer(&dev->timer);
if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {
dev->timer.data = (long) dev;
dev->timer.function = input_repeat_key;
dev->rep[REP_DELAY] = 250;
dev->rep[REP_PERIOD] = 33;
}
如 果input device没有定义getkeycode和setkeycode.则将其赋默认值。还记得在键盘驱动中
的分析吗?这两个操作函数就可以用来取键的扫描码 和设置键的扫描码。然后调用
device_add()将input_dev中封装的device注册到sysfs
if (!dev->getkeycode)
dev->getkeycode = input_default_getkeycode;
if (!dev->setkeycode)
dev->setkeycode = input_default_setkeycode;
snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id),
"input%ld", (unsigned long) atomic_inc_return(&input_no) - 1);
error = device_add(&dev->dev);
if (error)
return error;
path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);
printk(KERN_INFO "input: %s as %s\n",
dev->name ? dev->name : "Unspecified device", path ? path : "N/A");
kfree(path);
error = mutex_lock_interruptible(&input_mutex);
if (error) {
device_del(&dev->dev);
return error;
}
这 里就是重点了。将input device 挂到input_dev_list 链表上.然后,对每一个挂在
input_handler_list 的handler调用input_attach_handler().在这里的情况有好比设备模型中的
device和driver的匹配。所有的input device都挂在input_dev_list链上。所有的handle都挂
在input_handler_list上。
list_add_tail(&dev->node, &input_dev_list);
list_for_each_entry(handler, &input_handler_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
mutex_unlock(&input_mutex);
return 0;
}
static int input_attach_handler(struct input_dev *dev,
struct input_handler *handler)
{
const struct input_device_id *id;
int error;
如 果handle的blacklist被赋值。要先匹配blacklist中的数据跟dev->id的数据是否匹配。
匹配成功过后再来匹配handle->id 和dev->id 中的数据。如果匹配成功,则调用
handler->connect().
if (handler->blacklist && input_match_device(handler->blacklist, dev))
return -ENODEV;
id = input_match_device(handler->id_table, dev);
if (!id)
return -ENODEV;
error = handler->connect(handler, dev, id);
if (error && error != -ENODEV)
printk(KERN_ERR"input: failed to attach handler %s to device %s, "
"error: %d\n",handler->name, kobject_name(&dev->dev.kobj), error);
return error;
}
static const struct input_device_id *input_match_device(const struct
input_device_id *id,struct input_dev *dev)
{
如果id->flags 定义的类型匹配成功。或者是id->flags 没有定义,就会进入到MATCH_BIT
的匹配项了.从MATCH_BIT宏的定义可以看出。只有当iput device和input handler的id成
员在evbit, keybit,… swbit项相同才会匹配成功。而且匹配的顺序是从evbit, keybit到swbit.
只要有一项不同,就会循环到id中的下一项进行比较.
int i;
for (; id->flags || id->driver_info; id++) {
在id->flags 中定义了要匹配的项。定义INPUT_DEVICE_ID_MATCH_BUS。则是要比较
input device和input handler的总线类型。
if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)
if (id->bustype != dev->id.bustype)
continue;
INPUT_DEVICE_ID_MATCH_VENDOR ,INPUT_DEVICE_ID_MATCH_PRODUCT ,
INPUT_DEVICE_ID_MATCH_VERSION 分别要求设备厂商。设备号和设备版本.
if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)
if (id->vendor != dev->id.vendor)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)
if (id->product != dev->id.product)
continue;
if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)
if (id->version != dev->id.version)
continue;
MATCH_BIT(evbit, EV_MAX);
MATCH_BIT(,, KEY_MAX);
MATCH_BIT(relbit, REL_MAX);
MATCH_BIT(absbit, ABS_MAX);
MATCH_BIT(mscbit, MSC_MAX);
MATCH_BIT(ledbit, LED_MAX);
MATCH_BIT(sndbit, SND_MAX);
MATCH_BIT(ffbit, FF_MAX);
MATCH_BIT(swbit, SW_MAX);
return id;
}
return NULL;
}
MATCH_BIT宏的定义如下:
#define MATCH_BIT(bit, max) \
for (i = 0; i < BITS_TO_LONGS(max); i++) \
if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \
break; \
if (i != BITS_TO_LONGS(max)) \
continue;
此外如果已经注册了一些硬件设备,此后再注册一类新的Input Handler,则同样会对所有
已注册的Device调用新的Input Handler的Connect函数确定是否需要创建新的设备节点:
int input_register_handler(struct input_handler *handler)
{
struct input_dev *dev;
int retval;
retval = mutex_lock_interruptible(&input_mutex);
if (retval)
return retval;
INIT_LIST_HEAD(&handler->h_list);
if (handler->fops != NULL) {
handler->minor表示对应input 设备节点的次设备号.以handler->minor右移五位做为索引值
插入到input_table[ ]中..之后再来分析input_talbe[ ]的作用.
if (input_table[handler->minor >> 5]) {
retval = -EBUSY;
goto out;
}
input_table[handler->minor >> 5] = handler;
}
将handler挂到input_handler_list中
list_add_tail(&handler->node, &input_handler_list);
将其与挂在input_dev_list中的input device匹配.这个过程和input device的注册有相似的地
方.都是注册到各自的链表,.然后与另外一条链表的对象相匹配.
list_for_each_entry(dev, &input_dev_list, node)
input_attach_handler(dev, handler);
input_wakeup_procfs_readers();
out:
mutex_unlock(&input_mutex);
return retval;
}
int input_register_handle(struct input_handle *handle)
{
struct input_handler *handler = handle->handler;
struct input_dev *dev = handle->dev;
int error;
/*
* We take dev->mutex here to prevent race with
* input_release_device().
*/
error = mutex_lock_interruptible(&dev->mutex);
if (error)
return error;
list_add_tail_rcu(&handle->d_node, &dev->h_list);
mutex_unlock(&dev->mutex);
synchronize_rcu();
/*
* Since we are supposed tobe called from ->connect()
* which is mutually exclusive with ->disconnect()
* we can't be racing with input_unregister_handle()
* and so separate lock is not needed here.
*/
将handle挂到所对应input device的h_list链表上.还将handle挂到对应的handler的hlist链
表上.如果handler定义了start函数,将调用之.
list_add_tail(&handle->h_node, &handler->h_list);
if (handler->start)
handler->start(handle);
return 0;
}
我们已经看到了input device, handler和handle是怎么关联起来的了.以图的方式总结如下:
从上面的分析中可以看到一类Input Handler 可以和多个硬件设备相关联,创建多个设备节
点。而一个设备也可能与多个Input Handler相关联,创建多个设备节点。
直观起见,物理设备,InputHandler,逻辑设备之间的多对多关系可见下图:
物理设备,InputHandler,逻辑设备关系图
设备的打开和读写
用户程序通过Input Handler层创建的设备节点的Open,read,write等函数打开和读写输入
设备。
z Open
设备节点的Open函数,首先会调用一类具体的InputHandler的Open函数,处理一些
和该类型设备相关的通用事务,比如初始化事件缓冲区等。然后通过Input.c 中的
input_open_device函数调用驱动层中具体硬件设备的Open函数。
z Read
大多数Input Handler的Read函数等待在Event Layer层逻辑设备的wait队列上。当设
备驱动程序通过调用Input_event函数将输入以事件的形式通知给输入子系统的时候,相关
的Input Handler的event函数被调用,该event函数填充事件缓冲区后将等待队列唤醒。
在驱动层中,读取设备输入的一种可能的实现机制是扫描输入的函数睡眠在驱动设备
的等待队列上,在设备驱动的中断函数中唤醒等待队列,而后扫描输入函数将设备输入包
装成事件的形式通知给输入子系统。
z Write
2.6内核的代码中,通过调用Input_event将写入的数据以事件的形式再次通知给输入子
系统,而后在Input.c中根据事件的类型,将需要反馈给物理设备的事件通过调用物理设备的
Event函数传给设备驱动处理,如EV_LED事件:
void input_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
unsigned long flags;
//判断设备是否支持这类事件
if (is_event_supported(type, dev->evbit, EV_MAX)) {
spin_lock_irqsave(&dev->event_lock, flags);
//利用键盘输入来调整随机数产生器
add_input_randomness(type, code, value);
input_handle_event(dev, type, code, value);
spin_unlock_irqrestore(&dev->event_lock, flags);
}
}
首先,先判断设备产生的这个事件是否合法.如果合法,流程转入到input_handle_event()中.
代码如下:
static void input_handle_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
int disposition = INPUT_IGNORE_EVENT;
switch (type) {
case EV_SYN:
switch (code) {
case SYN_CONFIG:
disposition = INPUT_PASS_TO_ALL;
break;
case SYN_REPORT:
if (!dev->sync) {
dev->sync = 1;
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
}
break;
case EV_KEY:
//判断按键值是否被支持
if (is_event_supported(code, dev->keybit, KEY_MAX) &&
!!test_bit(code, dev->key) != value) {
if (value != 2) {
__change_bit(code, dev->key);
if (value)
input_start_autorepeat(dev, code);
}
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_SW:
if (is_event_supported(code, dev->swbit, SW_MAX) &&
!!test_bit(code, dev->sw) != value) {
__change_bit(code, dev->sw);
disposition = INPUT_PASS_TO_HANDLERS;
}
break;
case EV_ABS:
if (is_event_supported(code, dev->absbit, ABS_MAX)) {
value = input_defuzz_abs_event(value,
dev->abs[code], dev->absfuzz[code]);
if (dev->abs[code] != value) {
dev->abs[code] = value;
disposition = INPUT_PASS_TO_HANDLERS;
}
}
break;
case EV_REL:
if (is_event_supported(code, dev->relbit, REL_MAX) && value)
disposition = INPUT_PASS_TO_HANDLERS;
break;
case EV_MSC:
if (is_event_supported(code, dev->mscbit, MSC_MAX))
disposition = INPUT_PASS_TO_ALL;
break;
case EV_LED:
if (is_event_supported(code, dev->ledbit, LED_MAX) &&
!!test_bit(code, dev->led) != value) {
__change_bit(code, dev->led);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_SND:
if (is_event_supported(code, dev->sndbit, SND_MAX)) {
if (!!test_bit(code, dev->snd) != !!value)
__change_bit(code, dev->snd);
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_REP:
if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) {
dev->rep[code] = value;
disposition = INPUT_PASS_TO_ALL;
}
break;
case EV_FF:
if (value >= 0)
disposition = INPUT_PASS_TO_ALL;
break;
case EV_PWR:
disposition = INPUT_PASS_TO_ALL;
break;
}
if (type != EV_SYN)
dev->sync = 0;
if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event)
dev->event(dev, type, code, value);
if (disposition & INPUT_PASS_TO_HANDLERS)
input_pass_event (dev, type, code, value);
}
在 这里,我们忽略掉具体事件的处理.到最后,如果该事件需要input device 来完成的,就会将
disposition设置成INPUT_PASS_TO_DEVICE.如果需要handler来完成的,就将dispostion设
为INPUT_PASS_TO_DEVICE. 如果需要两者都参与, 将disposition 设置为
INPUT_PASS_TO_ALL.
需要输入设备参与的,回调设备的event函数.如果需要handler参与的.调用input_pass_event().
代码如下:
static void input_pass_event(struct input_dev *dev,
unsigned int type, unsigned int code, int value)
{
struct input_handle *handle;
rcu_read_lock();
handle = rcu_dereference(dev->grab);
if (handle)
handle->handler->event(handle, type, code, value);
else
list_for_each_entry_rcu(handle, &dev->h_list, d_node)
if (handle->open)
handle->handler->event(handle,type, code, value);
rcu_read_unlock();
}
如果input device被强制指定了handler,则调用该handler的event函数.
结合handle注册的分析.我们知道.会将handle挂到input device的h_list链表上.
如果没有为input device强制指定handler.就会遍历input device->h_list上的handle成员.如果
该handle被打开,则调用与输入设备对应的handler的event()函数.注意,只有在handle被打开
的情况下才会接收到事件.
另外,输入设备的handler强制设置一般是用带EVIOCGRAB标志的ioctl来完成的.如下是发
图的方示总结evnet的处理过程: