|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
在此文章之前,我们讲解的都是简单的字符驱动,涉及的内容有字符驱动的框架、自动创建设备节点、linux中断、poll机制、异步通知、同步互斥、非阻塞、定时器去抖动。
4 r5 b! t& ^1 ] C" K8 \, F/ d' U; z4 J' i y
上一节文章:1 g5 t. W- k9 k
8 ?5 [6 }/ a" o* E3 D
I; N4 d/ R- r7 O
在这一节里,我们要引入linux的分离分层的概念,linux输入子系统是一个很好的代表,在讲解如何编写input子系统的驱动之前,我们理所当然的要先好好认识一下input子系统的框架。* b! ]2 s8 b/ W" `
F" ~1 j+ s# M- F# _+ `
一、linux输入子系统的框架
2 F' `4 O- q( [$ y, q' o2 }
- H s. U6 h# _) N5 U9 E$ Q下图是input输入子系统框架,输入子系统由输入子系统核心层(Input Core),驱动层和事件处理层(Event Handler)
: O1 @, ]1 g% [+ [1 Z1 Z3 a. ?6 g9 k( E9 u& b
三部份组成。一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过
5 t8 A% d p k* W4 T3 \$ n- o8 I: }" V% f* k
input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。" Q/ N$ v2 O2 d: c7 {4 n
" z3 c- G$ h4 F4 w3 |' ^+ F2 E
( M2 Y1 {" s8 U7 T4 o
* _7 j+ e& X5 \2 V3 F二、drivers/input/input.c:9 s) S! t |. j. r4 t
. x% I ^2 F1 K/ m
入口函数input_init > err = register_chrdev(INPUT_MAJOR, "input", &input_fops);# ?! g) K/ E% R( A
* n! h, [* d& C6 b3 V; r7 Estatic int __init input_init(void)
! O, t6 c, ]. `1 t. X J/ W3 Q{% V k3 g& K1 j3 h" R; ]
int err;
: e( L+ O+ T- o' w2 I/ r* q+ W ...- o6 V* r& v4 V. u Z: K# r
/* 创建类 */9 U7 J. R: T3 N9 W8 p
err = class_register(&input_class);$ |# W' G. S! t4 D7 k3 G+ \& ?
.../ w, h, r8 A; ^) Y
/* 注册一个字符驱动,主设备号为13 */, Y5 h5 ^9 U' Z' X# x! B' Z
err = register_chrdev(INPUT_MAJOR, "input", &input_fops);3 h' a/ Z2 ` r5 |- e! i
...
; ]. u/ f1 }! b9 L7 e) y ]! [ I return 0;3 k* n3 L+ P z$ @
}3 O( W1 H5 ^1 @2 j% h: I
只有一个open函数,其他read,write函数呢?5 k8 h* ]& l* Y( W. M3 Q; B
static const struct file_operations input_fops = {2 v; Z M8 r; m. c
.owner = THIS_MODULE,. ? v& Y8 X2 H/ _5 P
.open = input_open_file,
: J# g4 I4 k+ e$ k4 [7 G};& ?5 @& F+ J. i' A
input_open_file函数
0 A- b6 x& T5 D# E% I" ?$ V, x& W
static int input_open_file(struct inode *inode, struct file *file) A6 \9 ?4 F! V9 V, `: S* r
{
: S1 T5 ^3 X8 y8 v K struct input_handler *handler;8 @) Y5 {. r/ ?
const struct file_operations *old_fops, *new_fops = NULL;( ]2 _3 ~ m8 |$ l: @
int err;
7 f/ ?/ L4 }( v2 K$ z ...) U# K" G: _5 L3 r
/* 以次设备号为下标,在input_table数组找到一项handler */
- y& L2 ^; A. I. ^' J. m+ u' d0 l) l handler = input_table[iminor(inode) >> 5];2 P, J* X2 }/ @9 {+ q
. J5 ^. u) J. D
/* 通过handler找到一个新的fops */& E: a/ l5 f5 g6 K
new_fops = fops_get(handler->fops);% B* s' o( v* h- D1 ?- n
...( z4 V. r4 U1 C1 M: R, f2 [9 ~; F
old_fops = file->f_op;
, P C' `) ^# M6 ^4 b) e$ K /* 从此file->f_op = new_fops */
6 |7 Y' v3 b( s8 b file->f_op = new_fops;
7 `" Z' Z/ m3 g, z; r ...! C6 w7 j( M; H' J4 N' o$ a
/* 用新的new_fops的打开函数 */9 Q8 ~+ y6 K2 G5 Q+ w- m
err = new_fops->open(inode, file);
1 j* D+ l- R0 i5 W! w1 `% X ..." y q8 u# o$ f# V# o
return err;4 P. r8 ~0 o8 _9 n9 S
}, _1 C! u" | Z
input_handlerj结构体成员. Q' G& a3 Y; V3 m! U1 ~- x1 Y! n
struct input_handler {
2 R2 Q1 Z( [8 H8 H% y' X7 ^1 v
( t5 u6 Q6 ~, q* d- L' t8 D void *private;# X7 N( W6 }0 E% A) S
8 Y D5 U3 Q5 s8 M) O8 O v
void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value);
! x- I4 F3 E6 t int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id);$ p7 K! r% E0 M0 o" {1 m1 \2 ]
void (*disconnect)(struct input_handle *handle);
$ Q6 K9 R+ n& x {6 G void (*start)(struct input_handle *handle);( W" K' t( Q4 R
( y% l1 x+ |% n, K
const struct file_operations *fops;" ^; S7 o( ]8 F/ u- K7 b H
int minor;- W) b4 y5 _$ M B$ L# B
const char *name;0 ?* ?9 F6 [3 O n. T
" E6 j4 c) r( }/ I7 s const struct input_device_id *id_table;: n. }+ @) C3 L# W* o% E$ t4 g
const struct input_device_id *blacklist;4 l- ^2 L" {2 M- u
5 q) `% N: r; W/ r) ` struct list_head h_list;
! @) s$ n$ i' [8 Q+ S struct list_head node;6 l L; N3 i( k+ c+ w
};0 o/ @9 L( @9 U4 J2 j' C
问:怎么读按键?, Q& |5 F$ A* ?, \! W* i
APP:read > ... > file->f_op->read
# z9 J; j) J* H& m+ m
# L- C5 s( J) u问:input_table数组由谁构造?" M) a6 r2 z* j+ [; k: \. A% B
; l: f4 D. t5 T @0 T5 F8 b5 D" n+ k答:input_register_handler
! J4 I' m7 o/ K! w
/ U0 B; P- E: X- U/ L7 s6 Q$ q+ p三、input_register_handler函数(注册input_handler)7 g5 M8 O" k+ I5 e6 J3 H
/ {% z ] P- {0 ~" m7 x# p5 \7 C" Q
. d& C R# z$ h( j0 m2 [& q5 fint input_register_handler(struct input_handler *handler)
|2 c! g4 k- f7 B8 i6 N{
' I$ U" \! x- i& ^- P( X# [ struct input_dev *dev;
! V. F9 s2 l$ p; i# |7 b: H ...5 E8 U# ?- N6 W; O8 \+ y( }' W6 }+ P
INIT_LIST_HEAD(&handler->h_list);8 A$ K1 O9 \- o6 a2 i0 V
...
' x3 p/ {3 Y5 P+ U3 q /* 将handler放入input_table数组 */ G" R1 |+ o3 X0 e" `" }/ m: a# [
input_table[handler->minor >> 5] = handler;
/ R' s- ^2 p# o( `! A7 v) O( Z ...8 w" a2 v Y) p" C" U1 g
/* 将handler放入input_handler_list链表 */" {# I& `! [2 d7 n; P% C$ y
list_add_tail(&handler->node, &input_handler_list);1 o p' I5 X6 ]3 g" l' U
...
" k7 Y7 ~. U, c1 t8 I. h( R /* 对于每个input_dev,调用input_attach_handler/ Q+ p: l$ G; A2 f1 w$ C6 P
* 根据input_handler的id_table判断能否支持这个input_dev
' @9 U |- {/ G */7 o8 d! b& ~6 q$ D- A c
list_for_each_entry(dev, &input_dev_list, node)
7 ] I3 D4 o, o: Z input_attach_handler(dev, handler);' n+ k( n; w: H& q- Y) t
...
/ h$ h. o/ F6 ^}
x8 p6 p- E( K) [; F4 ~. N x/ t& Z) {' N5 ]* m/ W0 t
四、input_register_device函数(注册inout_dev)
- m7 o1 O9 g* E- F- I; P' d. T% @/ H8 [9 v9 Y
int input_register_device(struct input_dev *dev)4 U0 v9 P& b# U9 T* V5 E
{
5 y$ }+ k7 _( S1 _; P: m ...
$ v# g% {2 N3 k( G struct input_handler *handler;' W" [0 g3 u& k5 a* p; G
...- I6 s. L) P" I4 k) `
device_add(&dev->dev);
. j1 g/ o) w5 r0 [6 g4 O3 ?9 Y ...
, q! M* ^ L' P/ P; u; m' J6 ~ /* 把input_dev放入input_dev_list链表 */
9 K! e3 k8 w6 v& J7 u; t list_add_tail(&dev->node, &input_dev_list);7 }6 m9 y- S' S# Z; F" ~$ O0 F/ f
...3 q: A# V, b; O# f, j" s+ \2 X
/* 对于每一个input_handler,都调用input_attach_handler
; w( p8 i" f3 J+ w2 v * 根据input_handler的id_table判断能否支持这个input_dev2 a) e# \) ^2 `8 n8 s( l
*/1 A" ]# c! A# l4 i7 U
list_for_each_entry(handler, &input_handler_list, node)+ p/ F6 D7 L2 @( _: E5 u( ^
input_attach_handler(dev, handler);+ _) k! i4 {1 Z# U
...
5 T+ W+ V, L$ n; t$ ^3 e$ W}
: |% e9 c8 o9 w6 \# {6 }
0 {; [8 |) ]/ r4 @3 ~五、input_attach_handler函数, S( T9 k u6 U
static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)2 j6 }+ _4 k: P! ]5 H
{
7 u' C$ b( F0 T9 W5 J const struct input_device_id *id;
% o5 R3 t: N7 | \& K1 Y ...
@" v l3 F, U F+ n0 f0 s /* 根据input_handler的id_table判断能否支持这个input_dev */
$ J8 `, _9 P, P: z1 k$ x input_match_device(handler->id_table, dev);3 N1 U% w" G G# C. t8 J( a
...
. G' R2 v+ r9 m( F /* 若支持,则调用handler的connect函数,建立连接 */
& }; i$ g( b' t$ R handler->connect(handler, dev, id);6 a( Q& l* Y! W S9 X
...$ _' c/ P0 V7 L. m& N: |: E+ L0 n
}5 \9 p5 ~/ o, N3 R& `' ^
$ w O% E! o9 w# t
小总结:
! N$ @% O1 A) W/ S; O4 x5 X注册input_dev或input_handler时,会两两比较左边的input_dev和右边的input_handler,根据input_handler的id_table判断这个input_handler能否支持这个input_dev,如果能支持,则调用input_handler的connect函数建立"连接"。
' V! {+ h0 \ L) T7 G8 |9 p2 i' z$ }3 w, t. X* s
问:如何建立连接connect?
" c$ r8 W; ~: P8 j# r! _5 I; d: R! H. B: ^
答:举例,evdev_connect函数5 h7 x0 M; @5 q0 E2 a0 `% X$ h
& }) S. \, \" x
! P$ k2 I3 ~; U O R/ P0 g
static int evdev_connect(struct input_handler *handler, struct input_dev *dev,0 Z% n1 S# o$ E. \
const struct input_device_id *id)
; g2 H! w( c* R$ {{8 q2 m; a2 v9 @ E) m! S5 M* S6 Z+ [
struct evdev *evdev;5 R. r: O4 K; M4 ^' [
..., Q$ e7 O8 _6 {/ K x; Y! Y
( b7 G \. w5 G8 D2 |# r; E /* 分配一个input_handle */) A' R X* P% @9 O; t1 L
evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL); 8 U$ J# x; G* ]- X7 M
...
6 h9 B( f" N1 j1 e6 ~/ ` snprintf(evdev->name, sizeof(evdev->name), "event%d", minor);
5 g) F! r6 z( y4 t4 P) Y& i evdev->exist = 1;
+ a$ N9 c! B: u$ G" A evdev->minor = minor;
& U4 r8 L& w3 l4 U
% \0 a9 ?2 }- Y/ J C# @2 c evdev->handle.dev = input_get_device(dev); // 指向左边的input_dev
+ ^, O! O# v4 R) { evdev->handle.name = evdev->name;
. T2 u# h, v9 y+ r9 M: x" g evdev->handle.handler = handler; // 指向右边的input_handler% W. ]1 [. j5 l1 s+ f1 k$ a |
evdev->handle.private = evdev;
; M+ Z$ o8 i$ @. V! G% g. \9 C% x6 u* \; \0 c5 [7 ~2 x i
/* 设置dev结构体成员 */8 T1 m! j3 K( O8 C. N$ j
dev_set_name(&evdev->dev, evdev->name);# R+ L6 @; G9 _; w/ V3 T
evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor);
3 B; z8 c2 o* P5 ^. J+ i6 E evdev->dev.class = &input_class;5 |! h) t; ]; J; \
evdev->dev.parent = &dev->dev;
: d3 S/ i, T0 }: N evdev->dev.release = evdev_free;9 d; U# g( }) r$ v/ C, |! m
device_initialize(&evdev->dev);) D, g! }( D) O+ U) U+ r
9 D$ M+ V# D) I* x$ L /* 注册 */9 }# {3 ]( R+ e# F. U4 Y4 w- Y- `6 q3 B
input_register_handle(&evdev->handle);6 i9 x# F' H6 {2 ^5 h
...
! z2 E2 ?6 c7 r' }$ v; q( w+ I}5 P2 u1 `, p" e8 q
input_handle结构体成员
( M8 G: }/ o2 Y0 a
6 e* x! a9 Q0 ?) t6 Xstruct input_handle {! [8 ^! i" G+ B/ j! k' V3 o; @
, ?" }; J8 w8 c4 `! g) r
void *private;9 _" I; V& z$ X0 E
/ C1 K' g4 f. I, S4 @ I- I
int open;. N" Y. G" x! v, ]9 O0 D' X
const char *name;
. X3 a, k9 R# b3 C5 x% F
, R" v1 Y+ |: t# p: _$ C struct input_dev *dev;
4 J. w$ B- }; E5 [ struct input_handler *handler;, R$ I" F7 v: D' \: U
# ?4 [& R* s" X; b2 k/ g- I struct list_head d_node;
6 W8 E$ V6 f) A, G" q struct list_head h_node;0 B* @# m1 m8 i0 [
};" E N& T6 L% E, C) r1 I# B
问:input_register_handle如何注册?
0 ?1 o: r3 E& Z) r) v# l+ P7 K; K8 j
$ i# H7 Q% L2 xint input_register_handle(struct input_handle *handle)3 V( }- G C& t/ K7 W9 L
{; G0 G, Q8 a4 d" E. m9 U7 `
struct input_handler *handler = handle->handler;$ r; l6 R+ U. ]; ^1 ^
struct input_dev *dev = handle->dev;9 J k; l; E% o6 D( F
...
* G3 A1 L, O. {7 _5 C2 f ' Q6 v( q& ~2 L' t; o
/* 把handle->d_node添加到dev->h_list
; b1 a/ W. H0 I; s7 ]7 c# v * 这样,就可以从dev->h_list找到handle,进而找到handler' c4 I( j8 g& \+ J9 a( H7 p
*/
6 j/ _2 y2 u. X3 N list_add_tail_rcu(&handle->d_node, &dev->h_list);1 s6 q: F6 J' z2 J, R+ I
...
; l, y# q$ L& ]* z; p" H; b: L4 u {% s, N. p# B
/* 把handle->h_node添加到handler->h_list
' Q$ B& g, a0 C9 w( Y * 这样,就可以从handler->h_list找到handle,进而找到dev
4 |# X9 Q% z! j1 n" s */2 T, K! U+ G7 E
list_add_tail(&handle->h_node, &handler->h_list);
% A4 [. B! M5 o4 }2 r; q ...
( z) S6 g. [% e' _- V8 D" P return 0;
l5 h+ J5 |* j- ~) z x}, p( M, [. f! c# ~- j# N0 m
小总结:+ N+ @. r+ E8 |% [" u
怎么建立连接connect?
! t. y @' j+ b: N- ^1. 分配一个input_handle结构体
" Z$ d3 ]" l1 ?; l' W$ H6 Q5 K% |2. * h5 k" }4 O5 o
input_handle.dev = input_dev; // 指向左边的input_dev( v, @0 _3 n7 F. U, o6 k
input_handle.handler = input_handler; // 指向右边的input_handler
3 V+ ]4 v# S9 [) Y% i; y9 N/ \5 Y3. 注册:
, Z5 C# n# k9 T0 f' e input_handler->h_list = &input_handle;, Y8 }$ u5 ]5 G8 l
inpu_dev->h_list = &input_handle;- p. P% x, z+ M; I9 `- e' C1 ?$ F
* `$ R. w5 T c0 r* f* J& q' S6 f( y六、怎么读按键?
- C& @5 |/ p0 e0 R3 y# N
/ w7 N6 u3 M* ^/ C v; I答:举例,evdev_read! \( L% \$ ^1 x% h& z8 D5 G: b4 P
# V0 j& D8 R! \5 C" S. ustatic ssize_t evdev_read(struct file *file, char __user *buffer,
1 d6 M9 `" F+ v& a0 u4 H size_t count, loff_t *ppos)# J" D, K. @' N3 M
{
( G) z8 n' e( F, l struct evdev_client *client = file->private_data;0 z& G$ `) E' ]5 k6 E2 [- z
struct evdev *evdev = client->evdev;
# n, }/ D2 e0 B1 M4 O" h( d: A struct input_event event;
" t' q3 F2 d5 j- F7 T K ...# _5 r& N6 {3 l' \5 @
1 T% E; i+ n5 e# S$ l! q /* 无数据并且是非阻塞方式打开,则立刻返回 */" O; N+ x3 v) T- V. x$ S( m4 ~
if (client->head == client->tail && evdev->exist &&# u" D; i0 n( g9 u" a' y
(file->f_flags & O_NONBLOCK))! y$ ~, p L: _5 s" ~3 V/ v
return -EAGAIN;
1 W6 p0 H) V! }% g
$ q) n4 N# j F4 X. c: g; ? /* 否则休眠 */
$ Q1 \' t% a, N/ `5 z- v" R retval = wait_event_interruptible(evdev->wait,4 R9 |* f) c6 x! j, \
client->head != client->tail || !evdev->exist);+ ?+ c! n) N/ @) ^6 J
...0 y% @( ?# L# U: x; M: G
}! p7 L7 S8 m( J) H. T4 `9 g
问:谁来唤醒?1 f5 Z1 C$ c. ]# x( O
搜索evdev->wait发现是evdev_event唤醒的% p/ c$ }: j/ y- a3 y& a8 a0 _
/ S' H, s3 N6 q0 B9 J0 T: N9 c
static void evdev_event(struct input_handle *handle," l/ q' E; ?' b% _
unsigned int type, unsigned int code, int value)* ]* T( p* M! L! z
{, O* }8 `) A3 G. x
struct evdev *evdev = handle->private;
$ g0 _7 K* ~7 r+ B3 B9 N struct evdev_client *client;
/ J5 R3 s- x7 w* _& _# | struct input_event event;( L' A# k* O7 y2 |
...0 P2 z( ?" u, O" ]1 _7 e7 S, R# o
/* 唤醒 */4 H6 {$ y# D3 j3 v
wake_up_interruptible(&evdev->wait);
/ p+ C4 U; W+ Y3 f$ s; L}; L* [, N7 H M
问:evdev_event被谁调用?; F: I+ {5 o+ A% ]. e6 Y6 m3 x, N
答:应该是硬件相关的代码,input_dev那层调用的在设备的中断服务程序里,确定事件是什么,然后调用相应的input_handler的event处理函数。4 l& B& ?5 P; H$ G5 v
/ s$ P7 z( t0 C( a举例,在drivers/input/keyboard/gpio_keys.c里的gpio_keys_isr函数% b7 j- y! j7 t( @- ~
/ q0 g) _& V6 qstatic irqreturn_t gpio_keys_isr(int irq, void *dev_id)
9 j4 h2 Q! {( V7 {, G{, {" r! R% Q9 W+ q% ^4 H1 v
struct gpio_button_data *bdata = dev_id;
D j& l$ D% T g; R; g) l struct gpio_keys_button *button = bdata->button;* _0 B4 X5 S$ w9 E/ u( j
...
( g3 Q, P4 J% `. o" d8 F /* 上报事件 */
/ Y7 E' ^" v8 s- ~( P gpio_keys_report_event(bdata);3 g. I* S1 [( n+ S0 @
return IRQ_HANDLED;0 B" v) c: O- I; m$ w- A( H
}
9 O3 ]& h; B: [9 |0 r+ Qgpio_keys_report_event函数6 O, K+ |* W9 c" M; {+ \! Q& d( c# I
- K$ P% F! e/ r! Q$ h4 Y4 ~( m& b
static void gpio_keys_report_event(struct gpio_button_data *bdata)
% I: n9 \( u- B- I4 g: C% V) T2 A{3 W6 }8 O' o1 _! y
struct gpio_keys_button *button = bdata->button;) O+ z# y4 h, \( E O* U% l
struct input_dev *input = bdata->input;- h3 o5 k+ l2 C2 U
unsigned int type = button->type ?: EV_KEY;
4 \( f0 n) X8 w. B8 q int state = (gpio_get_value(button->gpio) ? 1 : 0) ^ button->active_low;9 F6 @, P* s% X7 E* W( _1 A0 E
% R3 ~8 f0 l z4 A' x /* 上报事件 */
3 D/ c/ N6 }5 D1 \9 Y% g input_event(input, type, button->code, !!state);
. Z6 d# o% p# [" K) E1 \! p) k* W input_sync(input);
9 |( G- k) x3 N0 W8 y}% }- ?5 C" b' |6 {' d: e
问:input_event函数如何上报事件
& a* o; R9 i6 B' G( r/ {2 e' _) F% X5 n
答:# Z8 r* y4 @0 }' W F8 M: M5 Q% M2 ]
7 T3 L8 q1 C1 l2 ]" t6 p0 y3 h
input_event-->input_handle_event-->input_pass_event+ c* m* e4 \/ L3 e
list_for_each_entry_rcu(handle, &dev->h_list, d_node)7 v$ o8 ^3 n3 w. ~# Z
if (handle->open)/ R6 B& @1 d6 F, ]5 y( U
handle->handler->event(handle,5 a B+ D/ g9 d; Z1 W n# S
type, code, value);2 W# a0 @: y8 @* _. K- X' N
怎么写符合输入子系统框架的驱动程序?
4 Y; C0 R2 `; s% z9 ?
8 t, a0 i# ]+ M* W. M1 n- H1. 分配一个input_dev结构体
! q3 b& E: L& T3 J g9 H/ P2. 设置8 x# N8 L1 G" i& \
3. 注册* Q2 }% P3 P7 X
4. 硬件相关的代码,比如在中断服务程序里上报事件
5 B( j3 c2 ~' z; D, q" S# c; a8 q- I6 a1 M; B- T
% \0 p- O2 G) E; V+ q( r
2 c2 Y# Y+ Y$ Y
. q; r+ }# i, J2 O* {5 P! G7 ^; b& }
" {# O- ?0 O) I2 c! ] |
|