Skip to content

Commit bb6c861

Browse files
Benjamin TissoiresJiri Kosina
authored andcommitted
selftests/hid: hidraw: add more coverage for hidraw ioctls
Try to ensure all ioctls are having at least one test. Most of the scaffholding has been generated by claude-4-sonnet and then carefully reviewed. Signed-off-by: Benjamin Tissoires <bentiss@kernel.org> Signed-off-by: Jiri Kosina <jkosina@suse.com>
1 parent 02d6eee commit bb6c861

2 files changed

Lines changed: 352 additions & 0 deletions

File tree

tools/testing/selftests/hid/hid_common.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,12 @@ static int uhid_event(struct __test_metadata *_metadata, int fd)
230230
break;
231231
case UHID_SET_REPORT:
232232
UHID_LOG("UHID_SET_REPORT from uhid-dev");
233+
234+
answer.type = UHID_SET_REPORT_REPLY;
235+
answer.u.set_report_reply.id = ev.u.set_report.id;
236+
answer.u.set_report_reply.err = 0; /* success */
237+
238+
uhid_write(_metadata, fd, &answer);
233239
break;
234240
default:
235241
TH_LOG("Invalid event from uhid-dev: %u", ev.type);

tools/testing/selftests/hid/hidraw.c

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
/* Copyright (c) 2022-2024 Red Hat */
33

44
#include "hid_common.h"
5+
#include <linux/input.h>
6+
#include <string.h>
7+
#include <sys/ioctl.h>
58

69
/* for older kernels */
710
#ifndef HIDIOCREVOKE
@@ -215,6 +218,349 @@ TEST_F(hidraw, write_event_revoked)
215218
pthread_mutex_unlock(&uhid_output_mtx);
216219
}
217220

221+
/*
222+
* Test HIDIOCGRDESCSIZE ioctl to get report descriptor size
223+
*/
224+
TEST_F(hidraw, ioctl_rdescsize)
225+
{
226+
int desc_size = 0;
227+
int err;
228+
229+
/* call HIDIOCGRDESCSIZE ioctl */
230+
err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size);
231+
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESCSIZE ioctl failed");
232+
233+
/* verify the size matches our test report descriptor */
234+
ASSERT_EQ(desc_size, sizeof(rdesc))
235+
TH_LOG("expected size %zu, got %d", sizeof(rdesc), desc_size);
236+
}
237+
238+
/*
239+
* Test HIDIOCGRDESC ioctl to get report descriptor data
240+
*/
241+
TEST_F(hidraw, ioctl_rdesc)
242+
{
243+
struct hidraw_report_descriptor desc;
244+
int err;
245+
246+
/* get the full report descriptor */
247+
desc.size = sizeof(rdesc);
248+
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
249+
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed");
250+
251+
/* verify the descriptor data matches our test descriptor */
252+
ASSERT_EQ(memcmp(desc.value, rdesc, sizeof(rdesc)), 0)
253+
TH_LOG("report descriptor data mismatch");
254+
}
255+
256+
/*
257+
* Test HIDIOCGRDESC ioctl with smaller buffer size
258+
*/
259+
TEST_F(hidraw, ioctl_rdesc_small_buffer)
260+
{
261+
struct hidraw_report_descriptor desc;
262+
int err;
263+
size_t small_size = sizeof(rdesc) / 2; /* request half the descriptor size */
264+
265+
/* get partial report descriptor */
266+
desc.size = small_size;
267+
err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &desc);
268+
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRDESC ioctl failed with small buffer");
269+
270+
/* verify we got the first part of the descriptor */
271+
ASSERT_EQ(memcmp(desc.value, rdesc, small_size), 0)
272+
TH_LOG("partial report descriptor data mismatch");
273+
}
274+
275+
/*
276+
* Test HIDIOCGRAWINFO ioctl to get device information
277+
*/
278+
TEST_F(hidraw, ioctl_rawinfo)
279+
{
280+
struct hidraw_devinfo devinfo;
281+
int err;
282+
283+
/* get device info */
284+
err = ioctl(self->hidraw_fd, HIDIOCGRAWINFO, &devinfo);
285+
ASSERT_EQ(err, 0) TH_LOG("HIDIOCGRAWINFO ioctl failed");
286+
287+
/* verify device info matches our test setup */
288+
ASSERT_EQ(devinfo.bustype, BUS_USB)
289+
TH_LOG("expected bustype 0x03, got 0x%x", devinfo.bustype);
290+
ASSERT_EQ(devinfo.vendor, 0x0001)
291+
TH_LOG("expected vendor 0x0001, got 0x%x", devinfo.vendor);
292+
ASSERT_EQ(devinfo.product, 0x0a37)
293+
TH_LOG("expected product 0x0a37, got 0x%x", devinfo.product);
294+
}
295+
296+
/*
297+
* Test HIDIOCGFEATURE ioctl to get feature report
298+
*/
299+
TEST_F(hidraw, ioctl_gfeature)
300+
{
301+
__u8 buf[10] = {0};
302+
int err;
303+
304+
/* set report ID 1 in first byte */
305+
buf[0] = 1;
306+
307+
/* get feature report */
308+
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
309+
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGFEATURE ioctl failed, got %d", err);
310+
311+
/* verify we got the expected feature data */
312+
ASSERT_EQ(buf[0], feature_data[0])
313+
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
314+
ASSERT_EQ(buf[1], feature_data[1])
315+
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
316+
}
317+
318+
/*
319+
* Test HIDIOCGFEATURE ioctl with invalid report ID
320+
*/
321+
TEST_F(hidraw, ioctl_gfeature_invalid)
322+
{
323+
__u8 buf[10] = {0};
324+
int err;
325+
326+
/* set invalid report ID (not 1) */
327+
buf[0] = 2;
328+
329+
/* try to get feature report */
330+
err = ioctl(self->hidraw_fd, HIDIOCGFEATURE(sizeof(buf)), buf);
331+
ASSERT_LT(err, 0) TH_LOG("HIDIOCGFEATURE should have failed with invalid report ID");
332+
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
333+
}
334+
335+
/*
336+
* Test HIDIOCSFEATURE ioctl to set feature report
337+
*/
338+
TEST_F(hidraw, ioctl_sfeature)
339+
{
340+
__u8 buf[10] = {0};
341+
int err;
342+
343+
/* prepare feature report data */
344+
buf[0] = 1; /* report ID */
345+
buf[1] = 0x42;
346+
buf[2] = 0x24;
347+
348+
/* set feature report */
349+
err = ioctl(self->hidraw_fd, HIDIOCSFEATURE(3), buf);
350+
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSFEATURE ioctl failed, got %d", err);
351+
352+
/*
353+
* Note: The uhid mock doesn't validate the set report data,
354+
* so we just verify the ioctl succeeds
355+
*/
356+
}
357+
358+
/*
359+
* Test HIDIOCGINPUT ioctl to get input report
360+
*/
361+
TEST_F(hidraw, ioctl_ginput)
362+
{
363+
__u8 buf[10] = {0};
364+
int err;
365+
366+
/* set report ID 1 in first byte */
367+
buf[0] = 1;
368+
369+
/* get input report */
370+
err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
371+
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGINPUT ioctl failed, got %d", err);
372+
373+
/* verify we got the expected input data */
374+
ASSERT_EQ(buf[0], feature_data[0])
375+
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
376+
ASSERT_EQ(buf[1], feature_data[1])
377+
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
378+
}
379+
380+
/*
381+
* Test HIDIOCGINPUT ioctl with invalid report ID
382+
*/
383+
TEST_F(hidraw, ioctl_ginput_invalid)
384+
{
385+
__u8 buf[10] = {0};
386+
int err;
387+
388+
/* set invalid report ID (not 1) */
389+
buf[0] = 2;
390+
391+
/* try to get input report */
392+
err = ioctl(self->hidraw_fd, HIDIOCGINPUT(sizeof(buf)), buf);
393+
ASSERT_LT(err, 0) TH_LOG("HIDIOCGINPUT should have failed with invalid report ID");
394+
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
395+
}
396+
397+
/*
398+
* Test HIDIOCSINPUT ioctl to set input report
399+
*/
400+
TEST_F(hidraw, ioctl_sinput)
401+
{
402+
__u8 buf[10] = {0};
403+
int err;
404+
405+
/* prepare input report data */
406+
buf[0] = 1; /* report ID */
407+
buf[1] = 0x55;
408+
buf[2] = 0xAA;
409+
410+
/* set input report */
411+
err = ioctl(self->hidraw_fd, HIDIOCSINPUT(3), buf);
412+
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSINPUT ioctl failed, got %d", err);
413+
414+
/*
415+
* Note: The uhid mock doesn't validate the set report data,
416+
* so we just verify the ioctl succeeds
417+
*/
418+
}
419+
420+
/*
421+
* Test HIDIOCGOUTPUT ioctl to get output report
422+
*/
423+
TEST_F(hidraw, ioctl_goutput)
424+
{
425+
__u8 buf[10] = {0};
426+
int err;
427+
428+
/* set report ID 1 in first byte */
429+
buf[0] = 1;
430+
431+
/* get output report */
432+
err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
433+
ASSERT_EQ(err, sizeof(feature_data)) TH_LOG("HIDIOCGOUTPUT ioctl failed, got %d", err);
434+
435+
/* verify we got the expected output data */
436+
ASSERT_EQ(buf[0], feature_data[0])
437+
TH_LOG("expected feature_data[0] = %d, got %d", feature_data[0], buf[0]);
438+
ASSERT_EQ(buf[1], feature_data[1])
439+
TH_LOG("expected feature_data[1] = %d, got %d", feature_data[1], buf[1]);
440+
}
441+
442+
/*
443+
* Test HIDIOCGOUTPUT ioctl with invalid report ID
444+
*/
445+
TEST_F(hidraw, ioctl_goutput_invalid)
446+
{
447+
__u8 buf[10] = {0};
448+
int err;
449+
450+
/* set invalid report ID (not 1) */
451+
buf[0] = 2;
452+
453+
/* try to get output report */
454+
err = ioctl(self->hidraw_fd, HIDIOCGOUTPUT(sizeof(buf)), buf);
455+
ASSERT_LT(err, 0) TH_LOG("HIDIOCGOUTPUT should have failed with invalid report ID");
456+
ASSERT_EQ(errno, EIO) TH_LOG("expected EIO, got errno %d", errno);
457+
}
458+
459+
/*
460+
* Test HIDIOCSOUTPUT ioctl to set output report
461+
*/
462+
TEST_F(hidraw, ioctl_soutput)
463+
{
464+
__u8 buf[10] = {0};
465+
int err;
466+
467+
/* prepare output report data */
468+
buf[0] = 1; /* report ID */
469+
buf[1] = 0x33;
470+
buf[2] = 0xCC;
471+
472+
/* set output report */
473+
err = ioctl(self->hidraw_fd, HIDIOCSOUTPUT(3), buf);
474+
ASSERT_EQ(err, 3) TH_LOG("HIDIOCSOUTPUT ioctl failed, got %d", err);
475+
476+
/*
477+
* Note: The uhid mock doesn't validate the set report data,
478+
* so we just verify the ioctl succeeds
479+
*/
480+
}
481+
482+
/*
483+
* Test HIDIOCGRAWNAME ioctl to get device name string
484+
*/
485+
TEST_F(hidraw, ioctl_rawname)
486+
{
487+
char name[256] = {0};
488+
char expected_name[64];
489+
int err;
490+
491+
/* get device name */
492+
err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(name)), name);
493+
ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWNAME ioctl failed, got %d", err);
494+
495+
/* construct expected name based on device id */
496+
snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
497+
498+
/* verify the name matches expected pattern */
499+
ASSERT_EQ(strcmp(name, expected_name), 0)
500+
TH_LOG("expected name '%s', got '%s'", expected_name, name);
501+
}
502+
503+
/*
504+
* Test HIDIOCGRAWPHYS ioctl to get device physical address string
505+
*/
506+
TEST_F(hidraw, ioctl_rawphys)
507+
{
508+
char phys[256] = {0};
509+
char expected_phys[64];
510+
int err;
511+
512+
/* get device physical address */
513+
err = ioctl(self->hidraw_fd, HIDIOCGRAWPHYS(sizeof(phys)), phys);
514+
ASSERT_GT(err, 0) TH_LOG("HIDIOCGRAWPHYS ioctl failed, got %d", err);
515+
516+
/* construct expected phys based on device id */
517+
snprintf(expected_phys, sizeof(expected_phys), "%d", self->hid.dev_id);
518+
519+
/* verify the phys matches expected value */
520+
ASSERT_EQ(strcmp(phys, expected_phys), 0)
521+
TH_LOG("expected phys '%s', got '%s'", expected_phys, phys);
522+
}
523+
524+
/*
525+
* Test HIDIOCGRAWUNIQ ioctl to get device unique identifier string
526+
*/
527+
TEST_F(hidraw, ioctl_rawuniq)
528+
{
529+
char uniq[256] = {0};
530+
int err;
531+
532+
/* get device unique identifier */
533+
err = ioctl(self->hidraw_fd, HIDIOCGRAWUNIQ(sizeof(uniq)), uniq);
534+
ASSERT_GE(err, 0) TH_LOG("HIDIOCGRAWUNIQ ioctl failed, got %d", err);
535+
536+
/* uniq is typically empty in our test setup */
537+
ASSERT_EQ(strlen(uniq), 0) TH_LOG("expected empty uniq, got '%s'", uniq);
538+
}
539+
540+
/*
541+
* Test device string ioctls with small buffer sizes
542+
*/
543+
TEST_F(hidraw, ioctl_strings_small_buffer)
544+
{
545+
char small_buf[8] = {0};
546+
char expected_name[64];
547+
int err;
548+
549+
/* test HIDIOCGRAWNAME with small buffer */
550+
err = ioctl(self->hidraw_fd, HIDIOCGRAWNAME(sizeof(small_buf)), small_buf);
551+
ASSERT_EQ(err, sizeof(small_buf))
552+
TH_LOG("HIDIOCGRAWNAME with small buffer failed, got %d", err);
553+
554+
/* construct expected truncated name */
555+
snprintf(expected_name, sizeof(expected_name), "test-uhid-device-%d", self->hid.dev_id);
556+
557+
/* verify we got truncated name (first 8 chars, no null terminator guaranteed) */
558+
ASSERT_EQ(strncmp(small_buf, expected_name, sizeof(small_buf)), 0)
559+
TH_LOG("expected truncated name to match first %zu chars", sizeof(small_buf));
560+
561+
/* Note: hidraw driver doesn't guarantee null termination when buffer is too small */
562+
}
563+
218564
int main(int argc, char **argv)
219565
{
220566
return test_harness_run(argc, argv);

0 commit comments

Comments
 (0)