Linux input子系統 io控制字段


 

http://blog.csdn.net/guoshaobei/archive/2010/08/06/5792635.aspx

include/linux/input.h

 
#define EVIOCGVERSION        _IOR('E', 0x01, int)            /* get driver version */
#define EVIOCGID        _IOR('E', 0x02, struct input_id)    /* get device ID */
#define EVIOCGREP        _IOR('E', 0x03, int[2])            /* get repeat settings */
#define EVIOCSREP        _IOW('E', 0x03, int[2])            /* set repeat settings */
#define EVIOCGKEYCODE        _IOR('E', 0x04, int[2])            /* get keycode */
#define EVIOCSKEYCODE        _IOW('E', 0x04, int[2])            /* set keycode */

#define EVIOCGNAME(len)        _IOC(_IOC_READ, 'E', 0x06, len)        /* get device name */
#define EVIOCGPHYS(len)        _IOC(_IOC_READ, 'E', 0x07, len)        /* get physical location */
#define EVIOCGUNIQ(len)        _IOC(_IOC_READ, 'E', 0x08, len)        /* get unique identifier */

#define EVIOCGKEY(len)        _IOC(_IOC_READ, 'E', 0x18, len)        /* get global keystate */
#define EVIOCGLED(len)        _IOC(_IOC_READ, 'E', 0x19, len)        /* get all LEDs */
#define EVIOCGSND(len)        _IOC(_IOC_READ, 'E', 0x1a, len)        /* get all sounds status */
#define EVIOCGSW(len)        _IOC(_IOC_READ, 'E', 0x1b, len)        /* get all switch states */

#define EVIOCGBIT(ev,len)    _IOC(_IOC_READ, 'E', 0x20 + ev, len)    /* get event bits */
#define EVIOCGABS(abs)        _IOR('E', 0x40 + abs, struct input_absinfo)        /* get abs value/limits */
#define EVIOCSABS(abs)        _IOW('E', 0xc0 + abs, struct input_absinfo)        /* set abs value/limits */

#define EVIOCSFF        _IOC(_IOC_WRITE, 'E', 0x80, sizeof(struct ff_effect))    /* send a force effect to a force feedback device */
#define EVIOCRMFF        _IOW('E', 0x81, int)            /* Erase a force effect */
#define EVIOCGEFFECTS        _IOR('E', 0x84, int)            /* Report number of effects playable at the same time */

#define EVIOCGRAB        _IOW('E', 0x90, int)            /* Grab/Release device */

 

原文:http://www.linuxjournal.com/article/6429

盡管原文寫於2003,仍有參考價值。

 

<!-- @page { margin: 0.79in } P { margin-bottom: 0.08in } H4 { margin-bottom: 0.08in } PRE.cjk { font-family: "DejaVu Sans", monospace } TD P { margin-bottom: 0in } -->

The Linux USB Input Subsystem

Part 1

 

Linux USB輸入子系統是一種簡單的協調的管理所有輸入設備的方式。

本文討論 4部分內容:輸入子系統的描述;內核中輸入子系統的實現;輸入子系統的用戶空間 API;在你的程序中如何使用它。

 

什么是輸入子系統 ?
輸入子系統是 Linux內核用於管理各種輸入設備 (鍵盤,鼠標,遙控桿,書寫板等等 )的部分,用戶通過輸入子系統進行內核,命令行,圖形接口之間的交換。輸入子系統在內核里實現,因為設備經常要通過特定的硬件接口被訪問 (例如串口, ps/2 usb等等 ),這些硬件接口由內核保護和管理。內核給用戶導出一套固定的硬件無關的 input API,供用戶空間程序使用。

 

理解內核內部實現

輸入子系統分為三塊: input core drivers event handlers。他們之間的關系如圖 1所示。正常的路徑是從底層硬件到驅動,從驅動到 input core,從 input core event handler,從 event handler user space。此外,還存在一個返回路徑 (return path)。返回路徑允許給一個鍵盤設置 LED,給一個 force feedback joystick提供 motion commands。路徑的兩個方向(指從內核到用戶的方向和從用戶到內核的方向)使用相同的 event定義和不同的 type identifier

input subsystem

 

 

這三個核心模塊之間的交互主要通過 events數據結構來實現, events的數據結構定義如下:

Listing 1. event-dev-struct.txt

struct input_dev {

void *private;

char *name;
char *phys;
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)];
int ff_effects_max;

unsigned int keycodemax;
unsigned int keycodesize;
void *keycode;

unsigned int repeat_key;
struct timer_list timer;

struct pm_dev *pm_dev;
int 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)];

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 (*accept)(struct input_dev *dev,
struct file *file);
int (*flush)(struct input_dev *dev,
struct file *file);
int (*event)(struct input_dev *dev,
unsigned int type,
unsigned int code,
int value);
int (*upload_effect)(struct input_dev *dev,
struct ff_effect *effect);
int (*erase_effect)(struct input_dev *dev,
int effect_id);

struct list_head h_list;
struct list_head node;
};

type域顯示了被報告事件的類型,例如,一個 key press或者 button press relative motion(比如移動鼠標 )或者 absolute motion(比如移動游戲桿 ) code域告訴你是哪一個 key或者坐標軸在被操作; value域告訴你現在的狀態或者運動情況是什么。

例如,如果 type域是一個 key code域告訴你是哪一個 key value域告訴你該 key是被按下還是抬起。類似的,如果 type域是一個相對坐標軸, code域告訴你是哪一個坐標軸, value域告訴你移動的距離以及相對坐標軸的方向。

如果你以對角線的方向移動鼠標,同時移動滾輪,你將獲得三個相對事件:垂直軸上的運動事件 (x-axis),水平軸上的運動事件 (y-axis),滾輪的運動事件。

 

Event handlers給用戶空間提供接口,將標准事件格式轉換為特定 API所需要的格式。 Handlers也通常負責設備節點 (/dev entries)。最常見的 handler keyboard handler,它是大多數 C程序員熟悉的” standard input”

驅動通常提供底層硬件的接口,例如 USB PCI memory或者 I/O regions,或者 serial port I/O regions

在發送給 input core之前,驅動將用戶接口的底層硬件形式轉換為標准的事件格式。 Input core使用標准的內核 plugin design:使用 input_register_device()來添加設備,使用 input_unregister_device()來刪除設備。這些調用的參數是 struct input_device, listing-1所描述。盡管這個數據結構看起來很大,但是絕大多數的域被提供,用於保證驅動可以規定一個設備的能力,例如哪種事件類型,設備接受或者發送的 codes

 

除了管理驅動和 handlers input core也導出了一些有用的 /proc文件系統接口,用於查看當前活動的設備和事件 handlers。下面是查看 usb鼠標的例子 (cat /proc/bus/input/devices)

I: Bus=0003 Vendor=046d Product=c002 Version=0120
N: Name="Logitech USB-PS/2 Mouse M-BA47"
P: Phys=usb-00:01.2-2.2/input0
H: Handlers=mouse0 event2
B: EV=7
B: KEY=f0000 0 0 0 0 0 0 0 0
B: REL=103

 

I line:這行包含身份信息,顯示了 bus type 3 (usb) vendor product version等來來自 usb descriptoer關於鼠標的廠商信息。

N line:這行包含了名字信息。

P line:這行包含了物理設備信息。上述例子包含了 usb controller pci address, usb tree以及 input interface

H line: 這行包含了與設備關聯的 handler drivers

B line: 這些行包含了顯示設備能力的一些位域 (bitfield)

 

Listing 2. register.c

#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>

MODULE_LICENSE("GPL");

struct input_dev ex1_dev;

static int __init ex1_init(void)
{
/* extra safe initialization */
memset(&ex1_dev, 0, sizeof(struct input_dev));
init_input_dev(&ex1_dev);

/* set up descriptive labels */
ex1_dev.name = "Example 1 device";
/* phys is unique on a running system */
ex1_dev.phys = "A/Fake/Path";
ex1_dev.id.bustype = BUS_HOST;
ex1_dev.id.vendor = 0x0001;
ex1_dev.id.product = 0x0001;
ex1_dev.id.version = 0x0100;

/* this device has two keys (A and B) */
set_bit(EV_KEY, ex1_dev.evbit);
set_bit(KEY_B, ex1_dev.keybit);
set_bit(KEY_A, ex1_dev.keybit);

/* and finally register with the input core */
input_register_device(&ex1_dev);

return 0;
}

static void __exit ex1_exit(void)
{
input_unregister_device(&ex1_dev);
}

module_init(ex1_init);
module_exit(ex1_exit);

 

 

/proc接口是一種簡單使用的方法來測試一些簡單的驅動。考慮如下 listing 2的一個驅動例子,在 init()里注冊,在 removal()里注銷。該驅動示例使用 init_input_dev()來做一些初步的初始化工作:設置名字,物理描述符,身份描述符;然后設置 bit arrays來指定設備提供的事件類型是 EV_KEY,兩個可能的 codes KEY_A KEY_B。初始化代碼然后將設備注冊到 input core。如果你將這個示例代碼 modprobe到內核,你會從 /proc/bus/input/devices里看到如下信息:

I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="Example 1 device"
P: Phys=A/Fake/Path
H: Handlers=kbd event3
B: EV=3
B: KEY=10000 40000000

 

如果我想從設備驅動發送事件到 input core,我們需要調用 input_event()或者更便利的封裝函數,例如 input_report_key()/input_report_abs(),在 include/linux/input.h里定義。示例 listing 3使用了這些函數。

這個示例與 listing 2示例基本相同,但是增加了一個 timer,調用 ex2_timeout()。這個新函數發送了 4 KEY_A press 4 KEY_B press。注意這總共會產生 16 press 事件,這是因為每次 event由一次按鍵或一次釋放鍵產生。這些事件傳遞給 input core,然后傳遞給 keyboard handler,產生” aaaabbbb”或者” AAAABBBB”,依賴於 shift鍵是否被選擇。 timer 4秒后被設置循環運行, 4秒的時間確保你有最夠的時間移除模塊當你認為打印了足夠的測試信息。同樣注意需要調用 input_sync()函數。該函數用於通知 event handler (這里是 keyboard handler)設備已經傳遞完一組完整的數據。在 input_sync()函數被調用之前,

handler可能緩存 events

 

Listing 3. aaaabbbb.c

struct input_dev ex2_dev;

void ex2_timeout(unsigned long unused/*UNUSED*/)
{
int x;

for (x=0;x<4;x++) {
/* letter a or A */
input_report_key(&ex2_dev, KEY_A, 1);
input_sync(&ex2_dev);
input_report_key(&ex2_dev, KEY_A, 0);
input_sync(&ex2_dev);
}
for (x=0;x<4;x++) {
/* letter b or B */
input_report_key(&ex2_dev, KEY_B, 1);
input_sync(&ex2_dev);
input_report_key(&ex2_dev, KEY_B, 0);
input_sync(&ex2_dev);
}

/* set timer for four seconds */
mod_timer(&ex2_dev.timer,jiffies+4*HZ );
}

static int __init ex2_init(void)
{

... do initialization ...

/* set up a repeating timer */
init_timer(&ex2_dev.timer);
ex2_dev.timer.function = ex2_timeout;
ex2_dev.timer.expires = jiffies + HZ;
add_timer(&ex2_dev.timer);

return 0;
}

static void __exit ex2_exit(void)
{
del_timer_sync(&ex2_dev.timer);
input_unregister_device(&ex2_dev);
}

 

讓我們來看最后一個驅動例子,顯示相對信息如何提供,如 listing 4所示。這個驅動例子模仿了一個鼠標。初始化代碼配置設備有兩個坐標軸 (REL_X REL_Y)和一個 key(BTN_LEFT)。我們使用一個 timer來運行 ex3_timeout。這個 timer調用 input_report_rel來提供相對運動 (5個單步 ---相對運動是函數的的 3個參數 ),包含 30步向右, 30 步向下, 30步向左, 30步向上,因此光標構成了一個正方形。為了提供運動動畫,

timeout 20毫秒。再次強調的是, input_sync()是保證事件 handler處理一個完整的事件數據的,你需要調用 input_sync()來確保你的數據已經完整的傳遞給 input core。如果你向對角線移動,你需要這樣做:

...
input_report_rel(..., REL_X, ...);
input_report_rel(..., REL_Y, ...);
input_sync(...);
...

 

這樣確保了鼠標對角線移動,而不是現橫向移動,后豎向移動。

Listing 4. squares.c

void ex3_timeout(unsigned long unused /*UNUSED*/)
{
/* move in a small square */
if (state<30)
input_report_rel(&ex3_dev, REL_X, 5);
else if (state < 60)
input_report_rel(&ex3_dev, REL_Y, 5);
else if (state < 90)
input_report_rel(&ex3_dev, REL_X, -5);
else
input_report_rel(&ex3_dev, REL_Y, -5);

input_sync(&ex3_dev);

if ((state++) >= 120)
state = 0;

/* set timer for 0.02 seconds */
mod_timer(&ex3_dev.timer, jiffies+HZ/50);
}

static int __init ex3_init(void)
{
/* extra safe initialization */
memset(&ex3_dev, 0, sizeof(struct input_dev));
init_input_dev(&ex3_dev);

/* set up descriptive labels */
ex3_dev.name = "Example 3 device";
/* phys is unique on a running system */
ex3_dev.phys = "A/Fake/Path";
ex3_dev.id.bustype = BUS_HOST;
ex3_dev.id.vendor = 0x0001;
ex3_dev.id.product = 0x0003;
ex3_dev.id.version = 0x0100;

/* this device has two relative axes */
set_bit(EV_REL, ex3_dev.evbit);
set_bit(REL_X, ex3_dev.relbit);
set_bit(REL_Y, ex3_dev.relbit);

/* it needs a button to look like a mouse */
set_bit(EV_KEY, ex3_dev.evbit);
set_bit(BTN_LEFT, ex3_dev.keybit);

/* and finally register with the input core */
input_register_device(&ex3_dev);

/* set up a repeating timer */
init_timer(&ex3_dev.timer);
ex3_dev.timer.function = ex3_timeout;
ex3_dev.timer.expires = jiffies + HZ/10;
add_timer(&ex3_dev.timer);

return 0;
}

static void __exit ex3_exit(void)
{
del_timer_sync(&ex3_dev.timer);
input_unregister_device(&ex3_dev);
}

module_init(ex3_init);
module_exit(ex3_exit);

 

Handlers---到達用戶空間

我們看到設備驅動位於硬件設備和 input core之間,將硬件事件(通常是中斷)翻譯成 input events。為了使用 input events,我們使用 handlers,它提供了用戶空間的接口。

 

input子系統包含了你需要的大多數 handlers:一個提供 console keyboard handler;一個供應用程序使用的 mouse handler;一個 joystick handler以及一個 touchscreen handler。同樣有一個通用的 event handler,向用戶空間提供 basic input events。這意味着你不需要在內核里再寫一個 handler,因為你可以在用戶空間通過訪問 event handler完成你需要的功能。

 

 

<!-- @page { size: 8.5in 11in; margin: 0.79in } P { margin-bottom: 0.08in } H4 { margin-bottom: 0.08in } TD P { margin-bottom: 0in } -->

Using the Input Subsystem

part 2

 

Linux input子系統一個很重要的特性是它提供了 event interface。它通過字符設備節點對用戶空間導出了原生 event,允許用戶程序操作任何 event,不會遺失任何信息。

 

查找 event interface版本

使用 EVIOCGVERSION ioctl function。參數是 32 int類型,代表 major version (two high bytes) minor version (third byte) patch level (low byte) Listing 1顯示了使用 EVIOCGVERSION的例子:第 1個參數是 event device node的打開文件描述符。你需要傳遞一個指向 int數據的一個指針作為第 3個參數。

Listing 1. Sample EVIOCGVERSION Function

/* ioctl() accesses the underlying driver */
if (ioctl(fd, EVIOCGVERSION, &version)) {
perror("evdev ioctl");
}

/* the EVIOCGVERSION ioctl() returns an int */
/* so we unpack it and display it */
printf("evdev driver version is %d.%d.%d\n",
version >> 16, (version >> 8) & 0xff,
version & 0xff);

 

查找設備身份信息

event interface支持獲取設備的身份信息,使用 EVIOCGID ioctl function。參數是指向 input_id數據結構的指針。 input_id數據結構定義如 listing 2所示。 _u16數據類型是 16為無符號整型。

Figure 2. input_id Structure Definitions

struct input_id {
__u16 bustype;
__u16 vendor;
__u16 product;
__u16 version;
};

 

bustype 域包含了你需要的准確的數據信息。你應把它當作是一個非透明的枚舉類型,需要與通過 <linux/input.h>里定義的 BUG_x 類型比較獲知。 vendor/product/version是與 bus type相關的表示設備身份信息的域。現代設備(如 USB PCI)都使用了這些域,但是傳統的設備( serial mice, ps/2 keyboard, ISA sound card)沒有這些域。 EVIOCGID的使用如 listing 3所示。這個例子調用了 ioctl並打印了結果。

Listing 3. Sample EVIOCGID ioctl

/* suck out some device information */
if(ioctl(fd, EVIOCGID, &device_info)) {
perror("evdev ioctl");
}

/* the EVIOCGID ioctl() returns input_devinfo
* structure - see <linux/input.h>
* So we work through the various elements,
* displaying each of them
*/
printf("vendor %04hx product %04hx version %04hx",
device_info.vendor, device_info.product,
device_info.version);
switch ( device_info.bustype)
{
case BUS_PCI :
printf(" is on a PCI bus\n");
break;
case BUS_USB :
printf(" is on a Universal Serial Bus\n");
break;
...

 

除了 bus type/vendor/product/version等設備信息,某些設備還會提供一個有意義的名字字符串,使用 EVIOCGNAME獲取。如果名字串太長,則在返回的參數里被截取。 Listing 4顯示了它的使用的例子。

Listing 4. Example Truncated String

int fd = -1;
char name[256]= "Unknown";

if ((fd = open(argv[1], O_RDONLY)) < 0) {
perror("evdev open");
exit(1);
}

if(ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
perror("evdev ioctl");
}

printf("The device on %s says its name is %s\n",
argv[1], name);

close(fd);

 

下面是上述例子運行的結果樣例:

The device on /dev/input/event0 says its name is Logitech USB-PS/2 Optical Mouse

 

盡管設備身份信息和名字信息通常很有用,但是它也許並沒有提供足夠的信息告訴你當前在使用哪個設備。例如,你當前有兩個完全相同的遙控桿,你需要確定每個使用哪個端口。這通常屬於拓撲信息( topology information),可以使用 EVIOCGPHYS ioctl獲取。它返回一串字符串(或者一個負值錯誤碼)。 Listing 5顯示了它的使用的例子。

Listing 5. Using EVIOCGPHYS for Topology Information

if(ioctl(fd, EVIOCGPHYS(sizeof(phys)), phys) < 0) {
perror("event ioctl");
}
printf("The device on %s says its path is %s\n",
argv[1], phys);

 

它的運行結果樣例如下所示:

The device on /dev/input/event0 says its path is usb-00:01.2-2.1/input0

 

為了了解輸出的含義,你需要將其分成幾部分。 Usb部分意味着這使用 usb系統的一個物理拓撲。 00:01.2 usb host controller所在的 pci bus information (bus 0, slot 1, function 2) 2.1表示了從 root hub device的路徑,這里表示上行 hub接在 root hub的第 2個端口上,設備接在上行 hub的第 1個端口上。 Input0表示這是設備的第 1 event device 節點。大部分設備只有一個 event device節點,但是有些設備例外。比如多媒體鍵盤,它有一個 event device節點用於 normal keyboard,另一個 event device節點用於 multimedia keyboard。拓撲示例如下圖表示。

input subsystem topology

如果你在同一根連接線上將一個設備還成另外一個同樣的設備,你無法區分設備更換,除非每一個設備有一個唯一號,比如 serial number。使用 EVIOCGUNIQ可以獲取。 Listing 6是其示例。絕大多數設備沒有這樣的唯一號,所以你使用該 ioctl將返回一個空字符串。

Listing 6. Finding a Unique Identifier

if(ioctl(fd, EVIOCGUNIQ(sizeof(uniq)), uniq) < 0) {
perror("event ioctl");
}

printf("The device on %s says its identity is %s\n",
argv[1], uniq);

 

確定設備能力和特性

對於一些設備,也許知道設備的身份信息就足夠了,因為它允許你根據設備的使用情況處理設備的任何 case。但是這總做法的尺度不好。比如,你有一個設備僅有一個滑輪,你想使能處理滑輪的 handler,但是你並不想在 code里列出每個帶有滑輪的鼠標的 vendor/product信息。為此, event interface允許你對於某個設備確定有哪些功能和特性。 Event interface支持的 feature types有:

  • EV_KEY: absolute binary results, such as keys and buttons.

  • EV_REL: relative results, such as the axes on a mouse.

  • EV_ABS: absolute integer results, such as the axes on a joystick or for a tablet.

  • EV_MSC: miscellaneous uses that didn't fit anywhere else.

  • EV_LED: LEDs and similar indications.

  • EV_SND: sound output, such as buzzers.

  • EV_REP: enables autorepeat of keys in the input core.

  • EV_FF: sends force-feedback effects to a device.

  • EV_FF_STATUS: device reporting of force-feedback effects back to the host.

  • EV_PWR: power management events

這些僅僅是 type features。每個 type feature包含了很大范圍的不同的個體 feature。例如, EV_REL type區別了 x軸, y軸, z軸,橫輪,豎輪。同樣, EV_KEY type包含了成千上百個 keys buttons

使用 EVIOCGBIT ioctl可以獲取設備的能力和特性。它告知你設備是否有 key或者 button

EVIOCGBIT ioctl處理 4個參數 ( ioctl(fd, EVIOCGBIT(ev_type, max_bytes), bitfield)) ev_type是返回的 type feature 0是個特殊 case,表示返回設備支持的所有的 type features)。 max_bytes表示返回的最大字節數。 bitfield域是指向保存結果的內存指針。 return value表示保存結果的實際字節數,如果調用失敗,則返回負值。 Listing 7展現了其使用示例,測試了 /dev/input/event0設備節點支持哪些 type feature

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <fcntl.h>

#include <string.h>

#include <linux/input.h>

#define BITS_PER_LONG 32

#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)

static int test_bit(int nr, const volatile unsigned long *addr)

{

return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)));

}

int main(int argc, char ** argv)

{

int fd;

unsigned long *evtype_b = malloc(sizeof(int));

int yalv;

if ((fd = open(argv[1], O_RDONLY)) < 0) {

perror("evdev open");

exit(1);

}

memset(evtype_b, 0, sizeof(evtype_b));

if (ioctl(fd, EVIOCGBIT(0, EV_MAX), evtype_b) < 0) {

perror("evdev ioctl");

}

printf("Supported event types:\n");

for (yalv = 0; yalv < EV_MAX; yalv++) {

if (test_bit(yalv, evtype_b)) {

/* the bit is set in the event types list */

printf(" Event type 0x%02x ", yalv);

switch ( yalv)

{

case EV_SYN :

printf(" (Synch Events)\n");

break;

case EV_KEY :

printf(" (Keys or Buttons)\n");

break;

case EV_REL :

printf(" (Relative Axes)\n");

break;

case EV_ABS :

printf(" (Absolute Axes)\n");

break;

case EV_MSC :

printf(" (Miscellaneous)\n");

break;

case EV_LED :

printf(" (LEDs)\n");

break;

case EV_SND :

printf(" (Sounds)\n");

break;

case EV_REP :

printf(" (Repeat)\n");

break;

case EV_FF :

case EV_FF_STATUS:

printf(" (Force Feedback)\n");

break;

case EV_PWR:

printf(" (Power Management)\n");

break;

default:

printf(" (Unknown: 0x%04hx)\n",

yalv);

}

}

}

close(fd);

}

 

這個例子使用了 evtype_bit掩碼 EV_MAX(在 <linux/input.h>里定義),覺得 bit array需要多少內存。當 ioctl提交后,由 event layer填充 bit array。我們測試 bit array里的每一個 bit,來確定設備支持哪些 feature type。所有的設備都支持 EV_SYNC,該 bit input core來設置。

下面是運行結果的樣例:

如果是 keyboard,則

Supported event types:
Event type 0x00 (Synchronization Events)
Event type 0x01 (Keys or Buttons)
Event type 0x11 (LEDs)
Event type 0x14 (Repeat

 

如果是 mouse,則

Supported event types:
Event type 0x00 (Synchronization Events)
Event type 0x01 (Keys or Buttons)
Event type 0x02 (Relative Axes)

 

獲取設備 (from or to) input

 

當確定了設備的能力和特性后,你就知道了設備的事件類型。

獲取設備的 event通過 char設備的 read function。當你從 event device (例如 /dev/input/event0)里讀取 event時,你將獲得一系列 events,每個 event input_event結構表示。

Listing 8示例展示了讀取一個打開文件描述符 fd的事件。它過濾了不屬於 key的事件,並打印 input_event結構里的每個域。

Listing 8. Checking for Busy Spots

/* how many bytes were read */
size_t rb;
/* the events (up to 64 at once) */
struct input_event ev[64];

rb=read(fd,ev,sizeof(struct input_event)*64);

if (rb < (int) sizeof(struct input_event)) {
perror("evtest: short read");
exit (1);
}

for (yalv = 0;
yalv < (int) (rb / sizeof(struct input_event));
yalv++)
{
if (EV_KEY == ev[yalv].type)
printf("%ld.%06ld ",
ev[yalv].time.tv_sec,
ev[yalv].time.tv_usec,
printf("type %d code %d value %d\n",
ev[yalv].type,
ev[yalv].code, ev[yalv].value);
}

 

下面是運行的結果樣例:

Event: time 1033621164.003838, type 1, code 37, value 1
Event: time 1033621164.027829, type 1, code 38, value 0
Event: time 1033621164.139813, type 1, code 38, value 1
Event: time 1033621164.147807, type 1, code 37, value 0
Event: time 1033621164.259790, type 1, code 38, value 0
Event: time 1033621164.283772, type 1, code 36, value 1
Event: time 1033621164.419761, type 1, code 36, value 0
Event: time 1033621164.691710, type 1, code 14, value 1
Event: time 1033621164.795691, type 1, code 14, value 0

 

對於每個 key,你會獲得一個 key press和一個 key depress event

這是 char設備的標准 read接口,所以你不需要在程序里 busy loop。此外,如果你想同時獲得許多設備的 input事件時,使用 poll/select函數。

給設備發送信息,使用 char設備的標准 write函數,發送的數據必須是 input_event數據結構。

Listing 9展示了簡單的給設備寫數據的示例。這個例子先讓 Caps Lock LED打開,等 200毫秒,然后讓 Caps Lock LED關閉;然后讓 Num Lock LED打開,等 200毫秒,然后讓 Num Lock LED關閉。不斷循環這個過程,你會看到鍵盤上的指示燈交替閃爍。

Listing 9. Sample Data Write Function

struct input_event ev; /* the event */

/* we turn off all the LEDs to start */
ev.type = EV_LED;
ev.code = LED_CAPSL;
ev.value = 0;
retval = write(fd, &ev, sizeof(struct input_event));
ev.code = LED_NUML;
retval = write(fd, &ev, sizeof(struct input_event));
ev.code = LED_SCROLLL;
retval = write(fd, &ev, sizeof(struct input_event));

while (1)
{
ev.code = LED_CAPSL;
ev.value = 1;
write(fd, &ev, sizeof(struct input_event));
usleep(200000);
ev.value = 0;
write(fd, &ev, sizeof(struct input_event));

ev.code = LED_NUML;
ev.value = 1;
write(fd, &ev, sizeof(struct input_event));
usleep(200000);
ev.value = 0;
write(fd, &ev, sizeof(struct input_event));
}

 

現在我們清楚的知道如何接收或者發送一個事件 ---key按下 /抬起,鼠標移動等等。對於一些程序,可能還需要知道設備的一些全局狀態。比如,一個管理 keyboard的程序需要知道當前的哪些指示燈在亮,哪些鍵被釋放。

EVIOCGKEY ioctl用於確定一個設備的全局 key/button狀態,它在 bit array里設置了每個 key/button是否被釋放。 Listing 10展示了該示例。

Listing 10. Determining a Device's Global Key and Button

State

uint8_t key_b[KEY_MAX/8 + 1];

memset(key_b, 0, sizeof(key_b));
ioctl(fd, EVIOCGKEY(sizeof(key_b)), key_b);

for (yalv = 0; yalv < KEY_MAX; yalv++) {
if (test_bit(yalv, key_b)) {
/* the bit is set in the key state */
printf(" Key 0x%02x ", yalv);
switch ( yalv)
{
case KEY_RESERVED :
printf(" (Reserved)\n");
break;
case KEY_ESC :
printf(" (Escape)\n");
break;
/* other keys / buttons not shown */
case BTN_STYLUS2 :
printf(" (2nd Stylus Button )\n");
break;
default:
printf(" (Unknown key)\n");
}
}
}

 

EVIOCGLED EVIOCGSND ioctl EVIOCGKEY類似,分別表示當前 LED亮燈和聲音通道打開。 Listing 11展示了 EVIOCGLED的使用。

Listing 11. Using EVIOCGLED

memset(led_b, 0, sizeof(led_b));
ioctl(fd, EVIOCGLED(sizeof(led_b)), led_b);

for (yalv = 0; yalv < LED_MAX; yalv++) {
if (test_bit(yalv, led_b)) {
/* the bit is set in the LED state */
printf(" LED 0x%02x ", yalv);
switch ( yalv)
{
case LED_NUML :
printf(" (Num Lock)\n");
break;
case LED_CAPSL :
printf(" (Caps Lock)\n");
break;
/* other LEDs not shown here*/
default:
printf(" (Unknown LED: 0x%04hx)\n",
yalv);
}
}
}

 

使用 EVIOCGREP ioctl來獲取鍵盤的重復速率。 Listing 12展示了該示例。(重復速率指你按下鍵后,輸出的事件的次數。例如,按下 1鍵且不釋放, console里會輸出多個 1的速率)

Listing 12. Checking the Repeat Rate Settings

int rep[2];

if(ioctl(fd, EVIOCGREP, rep)) {
perror("evdev ioctl");
}

printf("[0]= %d, [1] = %d\n", rep[0], rep[1]);

其中, rep[0]表示在按鍵重復出現之前 delay的時間; rep[1]表示按鍵重復出現的時間間隔。

 

使用 EVIOCSREP ioctl來設置鍵盤的重復速率。 Listing 13展示了該示例。

Listing 13. Setting the Repeat Rates

int rep[2];

rep[0] = 2500;
rep[1] = 1000;

if(ioctl(fd, EVIOCSREP, rep)) {
perror("evdev ioctl");
}

rep[0]/rep[1]的含義同 Listing 12

 

有些 input driver支持 key mapping。使用 EVIOCGKEYCODE ioctl獲取一個 key對應的 scancode Listing 14/Listing 15展示了 key mapping的示例。注意有些鍵盤驅動並不支持這個特性(比如 USB鍵盤)。如果你想修改 key mapping,使用 EVIOCSKEYCODE ioctl即可。

Listing 14. Looping over Scancodes

int codes[2];

for (i=0; i<130; i++) {
codes[0] = i;
if(ioctl(fd, EVIOCGKEYCODE, codes)) {
perror("evdev ioctl");
}
printf("[0]= %d, [1] = %d\n",
codes[0], codes[1]);
}

 

Listing 15. Mapping Keys

int codes[2];

codes[0] = 58; /* M keycap */
codes[1] = 49; /* assign to N */

if(ioctl(fd, EVIOCSKEYCODE, codes)) {
perror("evdev ioctl");
}

 

使用 EVIOCGABS ioctl提供絕對坐標軸設備的狀態信息。它為每一個絕對坐標軸提供了一個 input_absinfo數據結構(參考 Listing 16)。如果你想查看設備的全局狀態,對每一個坐標軸調用 EVIOCGABS ioctl函數。 Listing 17展示了該示例。

Listing 16. input_absinfo for an Absolute Axis

struct input_absinfo {
__s32 value; // current value of the axis
__s32 minimum; // current limits of the axis
__s32 maximum; // current limits of the axis
__s32 fuzz;
__s32 flat;
};

 

Listing 17. Checking Global State by Axis

uint8_t abs_b[ABS_MAX/8 + 1];
struct input_absinfo abs_feat;

ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(abs_b)), abs_b);

printf("Supported Absolute axes:\n");

for (yalv = 0; yalv < ABS_MAX; yalv++) {
if (test_bit(yalv, abs_b)) {
printf(" Absolute axis 0x%02x ", yalv);
switch ( yalv)
{
case ABS_X :
printf("(X Axis) ");
break;
case ABS_Y :
printf("(Y Axis) ");
break;
default:
printf("(Unknown abs feature)");
}
if(ioctl(fd, EVIOCGABS(yalv), &abs_feat)) {
perror("evdev EVIOCGABS ioctl");
}
printf("%d (min:%d max:%d flat:%d fuzz:%d)",
abs_feat.value,
abs_feat.minimum,
abs_feat.maximum,
abs_feat.flat,
abs_feat.fuzz);
printf("\n");
}
}

 

Force Feedback (力回饋)

有三個 ioctl用於控制 force-feedback設備: EVIOCSFF EVIOCRMFF EVIOCGEFFECT。這三個 ioctl分別表示發送一個 force-feedback effect,刪除一個 force-feedback effect,獲取當前多少個 effect在同時使用。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com