| /* |
| * Copyright (c) 2015 Cedric Hnyda <[email protected]> |
| * |
| * This program is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU General Public License as |
| * published by the Free Software Foundation; either version 2 of |
| * the License, or (at your option) any later version. |
| * |
| * This program is distributed in the hope that it would be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| */ |
| |
| #include <linux/input.h> |
| #include <linux/uinput.h> |
| #include <fnmatch.h> |
| #include <errno.h> |
| #include <poll.h> |
| |
| #include "test.h" |
| #include "safe_macros.h" |
| #include "input_helper.h" |
| |
| #define VIRTUAL_DEVICE "virtual-device-ltp" |
| |
| #define VIRTUAL_DEVICE_REGEX "*virtual-device-ltp*" |
| |
| static int uinput_loaded; |
| static int check_device(void); |
| |
| static int try_open_device(void) |
| { |
| char path[256]; |
| char name[256]; |
| int ret, fd = -1; |
| unsigned int i; |
| |
| for (i = 0; i < 100; i++) { |
| snprintf(path, sizeof(path), "/dev/input/event%i", i); |
| |
| fd = open(path, O_RDONLY); |
| |
| if (fd < 0 && errno == ENOENT) |
| continue; |
| |
| if (fd < 0) { |
| tst_resm(TINFO | TERRNO, "failed to open %s", path); |
| break; |
| } |
| |
| ret = ioctl(fd, EVIOCGNAME(sizeof(name)), name); |
| if (ret < 0) { |
| tst_resm(TINFO | TERRNO, |
| "ioctl(%s, EVIOCGNAME(256), ...) failed", |
| path); |
| break; |
| } |
| |
| if (strcmp(name, VIRTUAL_DEVICE) == 0) |
| return fd; |
| close(fd); |
| } |
| |
| return -1; |
| } |
| |
| int open_device(void) |
| { |
| int fd; |
| int retries = 10; |
| |
| while (retries--) { |
| fd = try_open_device(); |
| if (fd > 0) |
| return fd; |
| tst_resm(TINFO, "Device not found, retrying..."); |
| usleep(10000); |
| } |
| |
| tst_brkm(TBROK, NULL, "Unable to find the input device"); |
| } |
| |
| static int try_load_uinput(void) |
| { |
| const char *argv[] = {"modprobe", "uinput", NULL}; |
| int ret; |
| |
| tst_resm(TINFO, "Trying to load uinput kernel module"); |
| |
| ret = tst_run_cmd(NULL, argv, NULL, NULL, 1); |
| if (ret) { |
| tst_resm(TINFO, "Failed to load the uinput module"); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static void unload_uinput(void) |
| { |
| const char *argv[] = {"modprobe", "-r", "uinput", NULL}; |
| int ret; |
| |
| tst_resm(TINFO, "Unloading uinput kernel module"); |
| |
| ret = tst_run_cmd(NULL, argv, NULL, NULL, 1); |
| if (ret) |
| tst_resm(TWARN, "Failed to unload uinput module"); |
| } |
| |
| static const char *uinput_paths[] = { |
| "/dev/input/uinput", |
| "/dev/uinput", |
| }; |
| |
| static int try_open_uinput(void) |
| { |
| unsigned int i; |
| int fd; |
| |
| for (i = 0; i < ARRAY_SIZE(uinput_paths); i++) { |
| fd = open(uinput_paths[i], O_WRONLY | O_NONBLOCK); |
| |
| if (fd > 0) { |
| tst_resm(TINFO, "Found uinput dev at %s", |
| uinput_paths[i]); |
| return fd; |
| } |
| |
| if (fd < 0 && errno != ENOENT) { |
| tst_brkm(TBROK | TERRNO, NULL, |
| "open(%s)", uinput_paths[i]); |
| } |
| } |
| |
| return -1; |
| } |
| |
| int open_uinput(void) |
| { |
| int fd; |
| int retries = 10; |
| |
| fd = try_open_uinput(); |
| if (fd > 0) |
| return fd; |
| |
| if (try_load_uinput()) { |
| while (retries--) { |
| fd = try_open_uinput(); |
| if (fd > 0) { |
| uinput_loaded = 1; |
| return fd; |
| } |
| tst_resm(TINFO, "Uinput dev not found, retrying..."); |
| usleep(10000); |
| } |
| |
| unload_uinput(); |
| } |
| |
| tst_brkm(TCONF, NULL, "Unable to find and open uinput"); |
| } |
| |
| void send_event(int fd, int event, int code, int value) |
| { |
| struct input_event ev = { |
| .type = event, |
| .code = code, |
| .value = value, |
| }; |
| |
| SAFE_WRITE(NULL, 1, fd, &ev, sizeof(ev)); |
| } |
| |
| void send_rel_move(int fd, int x, int y) |
| { |
| send_event(fd, EV_REL, REL_X, x); |
| send_event(fd, EV_REL, REL_Y, y); |
| send_event(fd, EV_SYN, 0, 0); |
| } |
| |
| void create_device(int fd) |
| { |
| int nb; |
| struct uinput_user_dev uidev = { |
| .name = VIRTUAL_DEVICE, |
| .id = { |
| .bustype = BUS_USB, |
| .vendor = 0x1, |
| .product = 0x1, |
| .version = 1, |
| } |
| }; |
| |
| SAFE_WRITE(NULL, 1, fd, &uidev, sizeof(uidev)); |
| SAFE_IOCTL(NULL, fd, UI_DEV_CREATE, NULL); |
| |
| for (nb = 100; nb > 0; nb--) { |
| if (check_device()) |
| return; |
| usleep(10000); |
| } |
| |
| destroy_device(fd); |
| tst_brkm(TBROK, NULL, "Failed to create device"); |
| } |
| |
| void setup_mouse_events(int fd) |
| { |
| SAFE_IOCTL(NULL, fd, UI_SET_EVBIT, EV_KEY); |
| SAFE_IOCTL(NULL, fd, UI_SET_KEYBIT, BTN_LEFT); |
| SAFE_IOCTL(NULL, fd, UI_SET_EVBIT, EV_REL); |
| SAFE_IOCTL(NULL, fd, UI_SET_RELBIT, REL_X); |
| SAFE_IOCTL(NULL, fd, UI_SET_RELBIT, REL_Y); |
| } |
| |
| void destroy_device(int fd) |
| { |
| SAFE_IOCTL(NULL, fd, UI_DEV_DESTROY, NULL); |
| SAFE_CLOSE(NULL, fd); |
| |
| if (uinput_loaded) |
| unload_uinput(); |
| } |
| |
| int check_event_code(struct input_event *iev, int event, int code) |
| { |
| return iev->type == event && iev->code == code; |
| } |
| |
| int check_sync_event(struct input_event *iev) |
| { |
| return check_event_code(iev, EV_SYN, SYN_REPORT); |
| } |
| |
| /* |
| * the value of stray_sync_event: |
| * 0: EV_SYN/SYN_REPORT events should not be received in /dev/input/eventX |
| * 1: EV_SYN/SYN_REPORT events may be received in /dev/input/eventX |
| * On an old kernel(before v3.7.0), EV_SYN/SYN_REPORT events are always |
| * received even though we send empty moves. |
| */ |
| int no_events_queued(int fd, int stray_sync_event) |
| { |
| struct pollfd fds = {.fd = fd, .events = POLLIN}; |
| int ret, res, sync_event_ignored; |
| struct input_event ev; |
| |
| if (tst_kvercmp(3, 7, 0) < 0 && stray_sync_event) |
| sync_event_ignored = 1; |
| |
| ret = poll(&fds, 1, 30); |
| |
| if (ret > 0) { |
| res = read(fd, &ev, sizeof(ev)); |
| |
| if (res == sizeof(ev)) { |
| if (sync_event_ignored && check_sync_event(&ev)) { |
| ret = 0; |
| tst_resm(TINFO, |
| "Ignoring stray sync event (known problem)"); |
| } else { |
| tst_resm(TINFO, |
| "Unexpected ev type=%i code=%i value=%i", |
| ev.type, ev.code, ev.value); |
| } |
| } |
| } |
| |
| return ret == 0; |
| } |
| |
| static int check_device(void) |
| { |
| FILE *file; |
| char line[256]; |
| |
| file = fopen("/proc/bus/input/devices", "r"); |
| if (!file) |
| return 0; |
| |
| while (fgets(line, 256, file)) { |
| if (fnmatch(VIRTUAL_DEVICE_REGEX, line, 0) == 0) |
| return 1; |
| } |
| |
| fclose(file); |
| |
| return 0; |
| } |