/* * SPDX-License-Identifier: BSD-2-Clause * * Phase 8.2 verification tool — daemon-side chardev exerciser. * * Opens /dev/daedalus-v4l2, validates the chardev round-trip: * 1. Open: must succeed (-EBUSY if another instance is open). * 2. Non-blocking read: must return -EAGAIN (empty queue). * 3. Trigger a kernel-injected PING via the debugfs trigger * (writes any byte to /sys/kernel/debug/daedalus_v4l2/test_ping). * 4. Read: should return a full PING message (header + 24-byte * payload). Magic / version / type / cookie must match. * 5. Write a matching PONG response. Kernel logs at pr_debug. * 6. Close. * * Build: cc -Wall -Wextra -O2 -I../include test_chardev_pingpong.c * -o test_chardev_pingpong */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "daedalus_v4l2_proto.h" #define CHARDEV_PATH "/dev/daedalus-v4l2" #define DEBUGFS_PING_PATH "/sys/kernel/debug/daedalus_v4l2/test_ping" #define die(fmt, ...) do { \ fprintf(stderr, "FAIL: " fmt ": %s\n", \ ##__VA_ARGS__, strerror(errno)); \ exit(1); \ } while (0) static void trigger_ping(void) { int fd = open(DEBUGFS_PING_PATH, O_WRONLY); if (fd < 0) die("open(%s) — is the module loaded? " "is the test_ping debugfs entry world-mounted " "via /sys/kernel/debug? Try `sudo mount -t debugfs none " "/sys/kernel/debug`", DEBUGFS_PING_PATH); if (write(fd, "1", 1) != 1) die("write(%s)", DEBUGFS_PING_PATH); close(fd); } int main(void) { uint8_t buf[1024]; struct daedalus_msg_hdr *hdr = (struct daedalus_msg_hdr *) buf; ssize_t n; int fd; printf("opening %s...\n", CHARDEV_PATH); fd = open(CHARDEV_PATH, O_RDWR | O_NONBLOCK); if (fd < 0) die("open(%s)", CHARDEV_PATH); /* Step 2: empty queue → non-blocking read returns -EAGAIN. */ n = read(fd, buf, sizeof(buf)); if (n != -1 || errno != EAGAIN) die("expected EAGAIN, got n=%zd errno=%d", n, errno); printf(" non-blocking read on empty queue: EAGAIN ✓\n"); /* Step 3: trigger a kernel-injected PING. */ trigger_ping(); printf(" injected PING via debugfs ✓\n"); /* Step 4: blocking read should now succeed. */ int flags = fcntl(fd, F_GETFL); if (flags < 0 || fcntl(fd, F_SETFL, flags & ~O_NONBLOCK) < 0) die("fcntl clear O_NONBLOCK"); n = read(fd, buf, sizeof(buf)); if (n < 0) die("read"); if ((size_t) n < sizeof(*hdr)) die("short read: %zd bytes (want >= %zu)", n, sizeof(*hdr)); if (hdr->magic != DAEDALUS_PROTO_MAGIC) die("bad magic 0x%08x (want 0x%08x)", hdr->magic, DAEDALUS_PROTO_MAGIC); if (hdr->version != DAEDALUS_PROTO_VERSION) die("bad version %u", hdr->version); if (hdr->type != DAEDALUS_MSG_PING) die("got type 0x%08x (want PING 0x%08x)", hdr->type, DAEDALUS_MSG_PING); if (hdr->cookie != 0x1234u) die("got cookie 0x%08x (want 0x1234)", hdr->cookie); if ((size_t) n != sizeof(*hdr) + hdr->payload_len) die("payload truncated"); printf(" read PING: magic ✓ version ✓ type=PING ✓ cookie=0x%x ✓ payload=%u bytes\n", hdr->cookie, hdr->payload_len); printf(" payload: \"%.*s\"\n", (int) hdr->payload_len, (const char *) (buf + sizeof(*hdr))); /* Step 5: write a matching PONG response. Kernel pr_debugs it. */ struct daedalus_msg_hdr pong = { .magic = DAEDALUS_PROTO_MAGIC, .version = DAEDALUS_PROTO_VERSION, .type = DAEDALUS_MSG_PONG, .cookie = 0x1234u, .payload_len = 0, .reserved = 0, }; n = write(fd, &pong, sizeof(pong)); if (n != (ssize_t) sizeof(pong)) die("write PONG (n=%zd)", n); printf(" wrote PONG (cookie=0x%x) ✓\n", pong.cookie); close(fd); printf("\nALL TESTS PASSED.\n"); printf("(check `sudo dmesg | tail` for the kernel-side\n" " 'chardev got response' pr_debug line.)\n"); return 0; }