輸入系統 常見的輸入設備有鍵盤、滑鼠、遙控桿、書寫板、觸摸屏等等,用戶通過這些輸入設備與Linux系統進行數據交換。 內核中怎樣表示一個輸入設備 // include/linux/input.h struct input_dev { const char *name; //設備名稱 const ch ...
輸入系統
常見的輸入設備有鍵盤、滑鼠、遙控桿、書寫板、觸摸屏等等,用戶通過這些輸入設備與Linux系統進行數據交換。
內核中怎樣表示一個輸入設備
// include/linux/input.h
struct input_dev {
const char *name; //設備名稱
const char *phys; //設備物理路徑
const char *uniq; //設備唯一標識碼
struct input_id id;
unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];
unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //支持什麼類型的輸入事件
unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //支持按鍵輸入事件的話,支持哪些按鍵(鍵盤)
unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //支持相對位移事件的話,支持哪些
unsigned long absbit[BITS_TO_LONGS(ABS_CNT)];
unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)];
unsigned long ledbit[BITS_TO_LONGS(LED_CNT)];
unsigned long sndbit[BITS_TO_LONGS(SND_CNT)];
unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];
unsigned long swbit[BITS_TO_LONGS(SW_CNT)];
.......
};
查看所有的輸入設備:
ls /dev/input/* -l
查看輸入設備的信息:
cat /proc/bus/input/devices
得到如下信息:
[root@imx6ull:~]# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 evbug
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0
I: Bus=0018 Vendor=dead Product=beef Version=28bb //設備ID(定義在input.h的struct input_id結構體)
N: Name="goodix-ts" //名稱
P: Phys=input/ts //物理地址
S: Sysfs=/devices/virtual/input/input1 //sys系統地址
U: Uniq= //標識號(無)
H: Handlers=event1 evbug
B: PROP=2 //設備屬性
B: EV=b //支持何種輸入事件
B: KEY=1c00 0 0 0 0 0 0 0 0 0 0 //設備具有的鍵
B: ABS=6e18000 0
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/soc0/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2 evbug
B: PROP=0
B: EV=3
B: KEY=c
APP可以獲得什麼數據
// include/linux/input.h
struct input_value {
__u16 type; //當前數據的事件類型
__u16 code; //當前事件類型下的哪一個事件
__s32 value; //
};
Type的內容:
// include/uapi/linux/input-event-codes.h
/*
* Event types
*/
#define EV_SYN 0x00 //同步事件
#define EV_KEY 0x01 //鍵盤事件
#define EV_REL 0x02 //相對位移事件
#define EV_ABS 0x03 //絕對位移事件
#define EV_MSC 0x04
#define EV_SW 0x05
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
code的內容(以EV_KEY舉例)
// include/uapi/linux/input-event-codes.h
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
#define KEY_5 6
#define KEY_6 7
#define KEY_7 8
#define KEY_8 9
#define KEY_9 10
#define KEY_0 11
獲取輸入設備信息實例
兩個ioctl的request參數說明(input.h)
request | 說明 |
---|---|
EVIOCGID | 返回輸入設備ID |
EVIOCGBIT(ev,len) | 獲取輸入設備支持的事件類型列表 |
ev值的說明:ev參數表示要獲取的事件類型,它是一個整數值
- 當ev=0,表示要獲取輸入設備支持的所有事件類型列表,包括鍵盤事件、滑鼠事件、相對事件、絕對事件、事件同步、雜項事件等。
- 當ev=1,表示要獲取輸入設備支持的鍵盤事件類型列表。
- 當ev=2,表示要獲取輸入設備支持的相對事件類型列表。
EVIOCGBIT的iotcl調用說明:必須使用
len = ioctl(fd, EVIOCGBIT(0, sizeof(evbit)), evbit);
//len是evbit的實際讀取大小,如果單獨使用sizeof(evbit)得到len,將發生段錯誤
源碼:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
int fd;
struct input_id id;
int err;
unsigned char byte;
unsigned int evbit[2];
int i;
int bit;
unsigned int len;
char *ev_names[] = {
"EV_SYN ",
"EV_KEY ",
"EV_REL ",
"EV_ABS ",
"EV_MSC ",
"EV_SW ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"NULL ",
"EV_LED ",
"EV_SND ",
"NULL ",
"EV_REP ",
"EV_FF ",
"EV_PWR ",
};
if(argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR);
if(fd == -1)
{
printf("can not open %s\n", argv[1]);
return -1;
}
err = ioctl(fd, EVIOCGID, &id); //返回輸入設備ID
if(err == 0)
{
printf("bustype = 0x%x\n", id.bustype );
printf("vendor = 0x%x\n", id.vendor );
printf("product = 0x%x\n", id.product );
printf("version = 0x%x\n", id.version );
}
len = ioctl(fd, EVIOCGBIT(0,sizeof(evbit)), evbit); //返回輸入事件類型
printf("support ev type:\n");
for(i = 0;i < len;i++)
{
byte = ((unsigned char *)evbit)[i];
for(bit = 0;bit < 8;bit++)
{
if(byte & (1<<bit))
{
printf("%s \n", ev_names[i*8 + bit]);
}
}
}
return 0;
}
實驗結果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event0
bustype = 0x19
vendor = 0x0
product = 0x0
version = 0x0
support ev type:
EV_SYN
EV_KEY
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event1
bustype = 0x18
vendor = 0xdead
product = 0xbeef
version = 0x28bb
support ev type:
EV_SYN
EV_KEY
EV_ABS
[root@imx6ull:~]# cat /proc/bus/input/devices
I: Bus=0019 Vendor=0000 Product=0000 Version=0000
N: Name="20cc000.snvs:snvs-powerkey"
P: Phys=snvs-pwrkey/input0
S: Sysfs=/devices/soc0/soc/2000000.aips-bus/20cc000.snvs/20cc000.snvs:snvs-powerkey/input/input0
U: Uniq=
H: Handlers=kbd event0 evbug
B: PROP=0
B: EV=3
B: KEY=100000 0 0 0
I: Bus=0018 Vendor=dead Product=beef Version=28bb
N: Name="goodix-ts"
P: Phys=input/ts
S: Sysfs=/devices/virtual/input/input1
U: Uniq=
H: Handlers=event1 evbug
B: PROP=2
B: EV=b
B: KEY=1c00 0 0 0 0 0 0 0 0 0 0
B: ABS=6e18000 0
I: Bus=0019 Vendor=0001 Product=0001 Version=0100
N: Name="gpio-keys"
P: Phys=gpio-keys/input0
S: Sysfs=/devices/soc0/gpio-keys/input/input2
U: Uniq=
H: Handlers=kbd event2 evbug
B: PROP=0
B: EV=3
B: KEY=c
結論:EV值與程式輸出的type結果一致
查詢和休眠喚醒方式讀輸入事件
所謂的阻塞與非阻塞,是在open處聲明。當設置為阻塞方式,如果沒有輸入事件,整個進程都在阻塞態
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#include <string.h>
/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
int fd;
unsigned int len;
struct input_event event; //read讀到的是input_event類型的結構體
if(argc < 2)
{
printf("Usage: %s <dev> [noblock]\n", argv[0]);
return -1;
}
if(argc == 3 && !strcmp(argv[2], "noblock"))
{
fd = open(argv[1], O_RDWR | O_NONBLOCK); //非阻塞(查詢)
}
else
{
fd = open(argv[1], O_RDWR);
}
if(fd == -1)
{
printf("can not open %s\n", argv[1]);
return -1;
}
while(1)
{
len = read(fd, &event, sizeof(event)); //阻塞方式下,進程阻塞在此
if(len == sizeof(event))
{
printf("type = 0x%x, code = 0x%x, value = 0x%x", event.type, event.code, event.value);
}
else
{
printf("read err %d", len);
}
}
return 0;
}
實驗現象:
- 查詢方式(非阻塞):反覆查詢,輸出"read err",直到操作輸入設備時,輸出內容更改為輸入事件內容
- 休眠-喚醒方式(阻塞):只有操作屏幕,才會輸出事件內容
POLL方式讀輸入事件
poll會在設定的時間內進行監聽,當改時間內有輸入事件返回或超過設定時間沒有事件返回,poll都將喚醒。poll/select函數可以監測多個文件,可以監測多種事件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#include <string.h>
#include <poll.h>
/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
int fd;
struct input_event event; //read讀到的是input_event類型的結構體
struct pollfd pollfd;
nfds_t nfds = 1; //同時打開一個文件
if(argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
fd = open(argv[1], O_RDWR | O_NONBLOCK); //非阻塞(查詢)
if(fd == -1)
{
printf("can not open %s\n", argv[1]);
return -1;
}
while(1)
{
pollfd.fd = fd;
pollfd.events = POLLIN;
pollfd.revents = 0; //revents初始化為0,當有輸入事件傳入,內核改寫revents
poll(&pollfd, nfds, 3000); //poll等待時間為3s
if(pollfd.revents == POLLIN) //只有poll函數返回了數據,才調用read
{
while(read(fd, &event, sizeof(event)) == sizeof(event)) //把一次獲取到的數據讀完再退出
{
printf("type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}
}
else if(pollfd.revents == 0)
{
printf("time out\n");
}
else
{
printf("read err\n");
}
}
return 0;
}
關於POLL實現多路復用IO
struct pollfd pollfd[n]; //n為文件個數
nfds_t nfds = n; //同時打開n個文件
.......
if(pollfd[0].revents == POLLIN){} //依次訪問revents
if(pollfd[1].revents == POLLIN){}
.......
非同步通知方式讀輸入事件
[補充]fcntl的五個功能:
- 複製一個現有的描述符(cmd=F_DUPFD).
- 獲得/設置文件描述符標記(cmd=F_GETFD或F_SETFD).
- 獲得/設置文件狀態標記(cmd=F_GETFL或F_SETFL).
- 獲得/設置非同步I/O所有權(cmd=F_GETOWN或F_SETOWN).
- 獲得/設置記錄鎖(cmd=F_GETLK , F_SETLK或F_SETLKW).
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <linux/input.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
int fd;
void sig_func(int sig)
{
struct input_event event;
while(read(fd, &event, sizeof(event)) == sizeof(event))
{
printf("type = 0x%x, code = 0x%x, value = 0x%x\n", event.type, event.code, event.value);
}
}
/* 用法:./get_input_info /dev/input/event0 */
int main(int argc, char const **argv)
{
int count = 0;
unsigned short flag;
if(argc != 2)
{
printf("Usage: %s <dev>\n", argv[0]);
return -1;
}
signal(SIGIO, sig_func); //1.註冊信號處理函數(信號類型為IO類型)
fd = open(argv[1], O_RDWR | O_NONBLOCK); //2.打開驅動(一定要用非阻塞方式,否則無輸入事件進程一直被阻塞)
if(fd == -1)
{
printf("can not open %s\n", argv[1]);
return -1;
}
fcntl(fd ,F_SETOWN, getpid()); //3.告知驅動程式app進程ID
flag = fcntl(fd, F_GETFL); //4.獲得文件狀態標記
fcntl(fd, F_SETFL, flag | FASYNC); //5.設置文件狀態標記(將進程添加到驅動fasync事件等待隊列)
while(1)
{
printf("count = %d\n", count++);
sleep(2);
}
return 0;
}
實驗結果:
[root@imx6ull:/mnt]# ./get_input_info /dev/input/event1
count = 0
count = 1
count = 2 //無輸入事件時正常計數
type = 0x3, code = 0x39, value = 0x6
type = 0x3, code = 0x35, value = 0x1a6
type = 0x3, code = 0x36, value = 0x131
type = 0x3, code = 0x30, value = 0x1f
type = 0x3, code = 0x3a, value = 0x1f
type = 0x1, code = 0x14a, value = 0x1
type = 0x0, code = 0x0, value = 0x0
count = 3
type = 0x3, code = 0x35, value = 0x1a7
type = 0x0, code = 0x0, value = 0x0
count = 4
type = 0x3, code = 0x35, value = 0x1a9
type = 0x0, code = 0x0, value = 0x0
count = 5
type = 0x3, code = 0x35, value = 0x1a8
type = 0x0, code = 0x0, value = 0x0
count = 6