diff mbox series

v4l2-compliance: test for poll bugs when disconnecting

Message ID 1ed22a80-9ec5-8a48-35cd-ac69950e54bd@xs4all.nl
State New
Headers show
Series v4l2-compliance: test for poll bugs when disconnecting | expand

Commit Message

Hans Verkuil Dec. 1, 2020, 12:49 p.m. UTC
If a process polls for an event (using select() or epoll), and the device is
unregistered, then it should wake up. This is normally difficult to test, but
the vivid driver has a Disconnect control that can emulate this.

This patch adds a testVividDisconnect() function to verify that select and
epoll behave correctly.

Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
---
diff mbox series

Patch

diff --git a/utils/v4l2-compliance/v4l2-compliance.cpp b/utils/v4l2-compliance/v4l2-compliance.cpp
index 2d4d10db..9e7b14c7 100644
--- a/utils/v4l2-compliance/v4l2-compliance.cpp
+++ b/utils/v4l2-compliance/v4l2-compliance.cpp
@@ -1438,6 +1438,14 @@  void testNode(struct node &node, struct node &node_m2m_cap, struct node &expbuf_
 	 * 	 S_SELECTION flags tests
 	 */

+	if (is_vivid &&
+	    node.controls.find(VIVID_CID_DISCONNECT) != node.controls.end()) {
+		if (node.node2)
+			node.node2->close();
+		node.node2 = NULL;
+		printf("\ttest Disconnect: %s\n\n", ok(testVividDisconnect(&node)));
+	}
+
 	restoreState();

 show_total:
diff --git a/utils/v4l2-compliance/v4l2-compliance.h b/utils/v4l2-compliance/v4l2-compliance.h
index 5cfe870b..c0cc57db 100644
--- a/utils/v4l2-compliance/v4l2-compliance.h
+++ b/utils/v4l2-compliance/v4l2-compliance.h
@@ -70,6 +70,10 @@  enum poll_mode {
 #define IS_ENCODER(node) ((node)->codec_mask & (JPEG_ENCODER | STATEFUL_ENCODER | STATELESS_ENCODER))
 #define IS_DECODER(node) ((node)->codec_mask & (JPEG_DECODER | STATEFUL_DECODER | STATELESS_DECODER))

+#define V4L2_CTRL_CLASS_VIVID 0x00f00000
+#define VIVID_CID_VIVID_BASE		(V4L2_CTRL_CLASS_VIVID | 0xf000)
+#define VIVID_CID_DISCONNECT            (VIVID_CID_VIVID_BASE + 65)
+
 struct test_query_ext_ctrl: v4l2_query_ext_ctrl {
 	__u64 menu_mask;
 };
@@ -296,6 +300,7 @@  int testQueryControls(struct node *node);
 int testSimpleControls(struct node *node);
 int testExtendedControls(struct node *node);
 int testEvents(struct node *node);
+int testVividDisconnect(struct node *node);
 int testJpegComp(struct node *node);

 // I/O configuration ioctl tests
diff --git a/utils/v4l2-compliance/v4l2-test-controls.cpp b/utils/v4l2-compliance/v4l2-test-controls.cpp
index d9c13b4e..018a404e 100644
--- a/utils/v4l2-compliance/v4l2-test-controls.cpp
+++ b/utils/v4l2-compliance/v4l2-test-controls.cpp
@@ -23,12 +23,12 @@ 
 #include <vector>

 #include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/epoll.h>

 #include "compiler.h"
 #include "v4l2-compliance.h"

-#define V4L2_CTRL_CLASS_VIVID 0x00f00000
-
 static int checkQCtrl(struct node *node, struct test_query_ext_ctrl &qctrl)
 {
 	struct v4l2_querymenu qmenu;
@@ -906,6 +906,89 @@  int testEvents(struct node *node)
 	return 0;
 }

+int testVividDisconnect(struct node *node)
+{
+	// Test that disconnecting a device will wake up any processes
+	// that are using select or poll.
+	//
+	// This can only be tested with the vivid driver that enabled
+	// the DISCONNECT control.
+
+	pid_t pid = fork();
+	if (pid == 0) {
+		struct timeval tv = { 5, 0 };
+		fd_set fds;
+
+		FD_ZERO(&fds);
+		FD_SET(node->g_fd(), &fds);
+		int res = select(node->g_fd() + 1, nullptr, nullptr, &fds, &tv);
+		// No POLLPRI seen
+		if (res != 1)
+			exit(1);
+		// POLLPRI seen, but didn't wake up
+		if (!tv.tv_sec)
+			exit(2);
+		v4l2_event ev = {};
+		// Woke up on POLLPRI, but VIDIOC_DQEVENT didn't return
+		// the ENODEV error.
+		if (doioctl(node, VIDIOC_DQEVENT, &ev) != ENODEV)
+			exit(3);
+		exit(0);
+	}
+	v4l2_control ctrl = { VIVID_CID_DISCONNECT, 0 };
+	sleep(1);
+	fail_on_test(doioctl(node, VIDIOC_S_CTRL, &ctrl));
+	int wstatus;
+	fail_on_test(waitpid(pid, &wstatus, 0) != pid);
+	fail_on_test(!WIFEXITED(wstatus));
+	if (WEXITSTATUS(wstatus))
+		return fail("select child exited with status %d\n", WEXITSTATUS(wstatus));
+
+	node->reopen();
+
+	pid = fork();
+	if (pid == 0) {
+		struct epoll_event ep_ev;
+		int epollfd = epoll_create1(0);
+
+		if (epollfd < 0)
+			exit(1);
+
+		ep_ev.events = 0;
+		if (epoll_ctl(epollfd, EPOLL_CTL_ADD, node->g_fd(), &ep_ev))
+			exit(2);
+
+		ep_ev.events = EPOLLPRI;
+		ep_ev.data.fd = node->g_fd();
+		if (epoll_ctl(epollfd, EPOLL_CTL_MOD, node->g_fd(), &ep_ev))
+			exit(3);
+		int ret = epoll_wait(epollfd, &ep_ev, 1, 5000);
+		if (ret == 0)
+			exit(4);
+		if (ret < 0)
+			exit(5);
+		if (ret != 1)
+			exit(6);
+		if (!(ep_ev.events & EPOLLPRI))
+			exit(7);
+		if (!(ep_ev.events & EPOLLERR))
+			exit(8);
+		v4l2_event ev = {};
+		// Woke up on POLLPRI, but VIDIOC_DQEVENT didn't return
+		// the ENODEV error.
+		if (doioctl(node, VIDIOC_DQEVENT, &ev) != ENODEV)
+			exit(9);
+		exit(0);
+	}
+	sleep(1);
+	fail_on_test(doioctl(node, VIDIOC_S_CTRL, &ctrl));
+	fail_on_test(waitpid(pid, &wstatus, 0) != pid);
+	fail_on_test(!WIFEXITED(wstatus));
+	if (WEXITSTATUS(wstatus))
+		return fail("epoll child exited with status %d\n", WEXITSTATUS(wstatus));
+	return 0;
+}
+
 int testJpegComp(struct node *node)
 {
 	struct v4l2_jpegcompression jc;