diff options
Diffstat (limited to 'tools/testing/selftests/hid/hidraw.c')
-rw-r--r-- | tools/testing/selftests/hid/hidraw.c | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/tools/testing/selftests/hid/hidraw.c b/tools/testing/selftests/hid/hidraw.c index 821db37ba4bb..d625772f8b7c 100644 --- a/tools/testing/selftests/hid/hidraw.c +++ b/tools/testing/selftests/hid/hidraw.c @@ -2,6 +2,9 @@ /* Copyright (c) 2022-2024 Red Hat */ #include "hid_common.h" +#include <linux/input.h> +#include <string.h> +#include <sys/ioctl.h> /* for older kernels */ #ifndef HIDIOCREVOKE @@ -215,6 +218,476 @@ TEST_F(hidraw, write_event_revoked) pthread_mutex_unlock(&uhid_output_mtx); } +/* + * Test HIDIOCGRDESCSIZE ioctl to get report descriptor size + */ +TEST_F(hidraw, ioctl_rdescsize) +{ + int desc_size = 0; + int err; + + /* call HIDIOCGRDESCSIZE ioctl */ + err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed"); + + /* verify the size matches our test report descriptor */ + ASSERT_EQ(desc_size, sizeof(rdesc)) + TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size); +} + +/* + * Test HIDIOCGRDESC ioctl to get report descriptor data + */ +TEST_F(hidraw, ioctl_rdesc) +{ + struct hidraw_report_descriptor desc; + int err; + + /* get the full report descriptor */ + desc.size = sizeof(rdesc); + err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed"); + + /* verify the descriptor data matches our test descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0) + TH_LOG("report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRDESC ioctl with smaller buffer size + */ +TEST_F(hidraw, ioctl_rdesc_small_buffer) +{ + struct hidraw_report_descriptor desc; + int err; + size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */ + + /* get partial report descriptor */ + desc.size = small_size; + err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer"); + + /* verify we got the first part of the descriptor */ + ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0) + TH_LOG("partial report descriptor data mismatch"); +} + +/* + * Test HIDIOCGRAWINFO ioctl to get device information + */ +TEST_F(hidraw, ioctl_rawinfo) +{ + struct hidraw_devinfo devinfo; + int err; + + /* get device info */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo); + ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed"); + + /* verify device info matches our test setup */ + ASSERT_EQ(devinfo.bustype, BUS_USB) + TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype); + ASSERT_EQ(devinfo.vendor, 0x0001) + TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor); + ASSERT_EQ(devinfo.product, 0x0a37) + TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product); +} + +/* + * Test HIDIOCGFEATURE ioctl to get feature report + */ +TEST_F(hidraw, ioctl_gfeature) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get feature report */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err); + + /* verify we got the expected feature data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGFEATURE ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_gfeature_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get feature report */ + err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test ioctl with incorrect nr bits + */ +TEST_F(hidraw, ioctl_invalid_nr) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0), got errno %d", errno); + + /* + * craft an ioctl command with wrong _IOC_NR bits + */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x00, sizeof(buf)); /* 0 is not valid */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0), got errno %d", errno); + + /* also test with bigger number */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x42, sizeof(buf)); /* 0x42 is not valid as well */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-only with wrong _IOC_NR (0x42) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-only _IOC_NR (0x42), got errno %d", errno); + + /* also test with bigger number: 0x42 is not valid as well */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x42, sizeof(buf)); + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl read-write with wrong _IOC_NR (0x42) should have failed"); + ASSERT_EQ(errno, ENOTTY) + TH_LOG("expected ENOTTY for wrong read-write _IOC_NR (0x42), got errno %d", errno); +} + +/* + * Test ioctl with incorrect type bits + */ +TEST_F(hidraw, ioctl_invalid_type) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_TYPE bits + */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'I', 0x01, sizeof(buf)); /* 'I' should be 'H' */ + + /* test the ioctl */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("ioctl with wrong _IOC_TYPE (I) should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_NR, got errno %d", errno); +} + +/* + * Test HIDIOCGFEATURE ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_gfeature_invalid_dir) +{ + __u8 buf[10] = {0}; + int err; + unsigned int bad_cmd; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGFEATURE should have _IOC_WRITE|_IOC_READ, let's use only _IOC_WRITE + */ + bad_cmd = _IOC(_IOC_WRITE, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */ + + /* try to get feature report with wrong direction bits */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); + + /* also test with only _IOC_READ */ + bad_cmd = _IOC(_IOC_READ, 'H', 0x07, sizeof(buf)); /* should be _IOC_WRITE|_IOC_READ */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); +} + +/* + * Test read-only ioctl with incorrect _IOC_DIR bits + */ +TEST_F(hidraw, ioctl_readonly_invalid_dir) +{ + char buf[256] = {0}; + int err; + unsigned int bad_cmd; + + /* + * craft an ioctl command with wrong _IOC_DIR bits + * HIDIOCGRAWNAME should have _IOC_READ, let's use _IOC_WRITE + */ + bad_cmd = _IOC(_IOC_WRITE, 'H', 0x04, sizeof(buf)); /* should be _IOC_READ */ + + /* try to get device name with wrong direction bits */ + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); + + /* also test with _IOC_WRITE|_IOC_READ */ + bad_cmd = _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x04, sizeof(buf)); /* should be only _IOC_READ */ + + err = ioctl(self->hidraw_fd, bad_cmd, buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGRAWNAME with wrong _IOC_DIR should have failed"); + ASSERT_EQ(errno, EINVAL) TH_LOG("expected EINVAL for wrong _IOC_DIR, got errno %d", errno); +} + +/* + * Test HIDIOCSFEATURE ioctl to set feature report + */ +TEST_F(hidraw, ioctl_sfeature) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare feature report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x42; + buf[2] = 0x24; + + /* set feature report */ + err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGINPUT ioctl to get input report + */ +TEST_F(hidraw, ioctl_ginput) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get input report */ + err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err); + + /* verify we got the expected input data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGINPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_ginput_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get input report */ + err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSINPUT ioctl to set input report + */ +TEST_F(hidraw, ioctl_sinput) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare input report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x55; + buf[2] = 0xAA; + + /* set input report */ + err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGOUTPUT ioctl to get output report + */ +TEST_F(hidraw, ioctl_goutput) +{ + __u8 buf[10] = {0}; + int err; + + /* set report ID 1 in first byte */ + buf[0] = 1; + + /* get output report */ + err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err); + + /* verify we got the expected output data */ + ASSERT_EQ(buf[0], feature_data[0]) + TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]); + ASSERT_EQ(buf[1], feature_data[1]) + TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]); +} + +/* + * Test HIDIOCGOUTPUT ioctl with invalid report ID + */ +TEST_F(hidraw, ioctl_goutput_invalid) +{ + __u8 buf[10] = {0}; + int err; + + /* set invalid report ID (not 1) */ + buf[0] = 2; + + /* try to get output report */ + err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf); + ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID"); + ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno); +} + +/* + * Test HIDIOCSOUTPUT ioctl to set output report + */ +TEST_F(hidraw, ioctl_soutput) +{ + __u8 buf[10] = {0}; + int err; + + /* prepare output report data */ + buf[0] = 1; /* report ID */ + buf[1] = 0x33; + buf[2] = 0xCC; + + /* set output report */ + err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf); + ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err); + + /* + * Note: The uhid mock doesn't validate the set report data, + * so we just verify the ioctl succeeds + */ +} + +/* + * Test HIDIOCGRAWNAME ioctl to get device name string + */ +TEST_F(hidraw, ioctl_rawname) +{ + char name[256] = {0}; + char expected_name[64]; + int err; + + /* get device name */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err); + + /* construct expected name based on device id */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id); + + /* verify the name matches expected pattern */ + ASSERT_EQ(strcmp(name, expected_name), 0) + TH_LOG("expected name '%s', got '%s'", expected_name, name); +} + +/* + * Test HIDIOCGRAWPHYS ioctl to get device physical address string + */ +TEST_F(hidraw, ioctl_rawphys) +{ + char phys[256] = {0}; + char expected_phys[64]; + int err; + + /* get device physical address */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys); + ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err); + + /* construct expected phys based on device id */ + snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id); + + /* verify the phys matches expected value */ + ASSERT_EQ(strcmp(phys, expected_phys), 0) + TH_LOG("expected phys '%s', got '%s'", expected_phys, phys); +} + +/* + * Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string + */ +TEST_F(hidraw, ioctl_rawuniq) +{ + char uniq[256] = {0}; + int err; + + /* get device unique identifier */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq); + ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err); + + /* uniq is typically empty in our test setup */ + ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq); +} + +/* + * Test device string ioctls with small buffer sizes + */ +TEST_F(hidraw, ioctl_strings_small_buffer) +{ + char small_buf[8] = {0}; + char expected_name[64]; + int err; + + /* test HIDIOCGRAWNAME with small buffer */ + err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf); + ASSERT_EQ(err, sizeof(small_buf)) + TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err); + + /* construct expected truncated name */ + snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id); + + /* verify we got truncated name (first 8 chars, no null terminator guaranteed) */ + ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0) + TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf)); + + /* Note: hidraw driver doesn't guarantee null termination when buffer is too small */ +} + int main(int argc, char **argv) { return test_harness_run(argc, argv); |