Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

hidapi documentation about number of bytes written and read #589

Open
mcuee opened this issue Jun 16, 2023 · 44 comments
Open

hidapi documentation about number of bytes written and read #589

mcuee opened this issue Jun 16, 2023 · 44 comments
Labels
documentation Improvements or additions to documentation

Comments

@mcuee
Copy link
Member

mcuee commented Jun 16, 2023

Discusison here:

As @todbot mentioned, this has been an issue bugging him. Same for me.

Testing device: Circuit Python rawhid example, no report IDs.

boot.py

import usb_hid

RAWHID_REPORT_DESCRIPTOR = bytes((
    0x06, 0x00, 0xFF,  # Usage Page (Vendor Defined 0xFF00)
    0x09, 0x01,        # Usage (0x01)
    0xA1, 0x01,        # Collection (Application)
    0x09, 0x02,        #   Usage (0x02)
    0x15, 0x00,        #   Logical Minimum (0)
    0x26, 0xFF, 0x00,  #   Logical Maximum (255)
    0x75, 0x08,        #   Report Size (8)
    0x95, 0x40,        #   Report Count (64)
    0x81, 0x02,        #   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0x09, 0x03,        #   Usage (0x03)
    0x15, 0x00,        #   Logical Minimum (0)
    0x26, 0xFF, 0x00,  #   Logical Maximum (255)
    0x75, 0x08,        #   Report Size (8)
    0x95, 0x40,        #   Report Count (64)
    0x91, 0x02,        #   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
    0xC0,              # End Collection
))

raw_hid = usb_hid.Device(
    report_descriptor=RAWHID_REPORT_DESCRIPTOR,
    usage_page=0xFF00,
    usage=0x01,
    report_ids=(0,),
    in_report_lengths=(64,),
    out_report_lengths=(64,),
)

usb_hid.enable((raw_hid,))

code.py

import usb_hid
import time

d = usb_hid.devices[0]

while True:
    report = bytearray(64)  # must be same size as specified in HID Report Descriptor in boot.py    

    report[0] = 1
    report[1] = 2
    report[2] = 3
    report[3] = 4
    report[63] = 64
    d.send_report(report)
    time.sleep(1)
    print(d.get_last_received_report())

hidapitester output under Windows, it says 65 bytes written

PS C:\work\hid\hidapitester> .\hidapitester --vidpid 2e8a:102e --open --length 64 --send-output 1,2,3,4,5,6,7,8
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 65 bytes:
 01 02 03 04 05 06 07 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Closing device

hidapitester output under Linux (Ubuntu 20.04 x64): it says 64 bytes written.

mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester --vidpid 2e8a:102e --open --length 64 --send-output 1,2,3,4,5,6,7,8
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 64 bytes:
 01 02 03 04 05 06 07 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Closing device

hidapitester output under macOS 13.4 (Mac Mini M1): it says 64 bytes written.

mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid 2e8a:102e --open --length 64 --send-output 1,2,3,4,5,6,7,8
Opening device, vid/pid: 0x2E8A/0x102E
Writing output report of 64-bytes...wrote 64 bytes:
 01 02 03 04 05 06 07 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Closing device```

hidtest info about the device (using Linux to get the real HID report descriptors)

Device Found
type: 2e8a 102e
path: /dev/hidraw3
serial_number: DE6185100F4D6522
Manufacturer: VCC-GND Studio
Product: YD-RP2040
Release: 100
Interface: 3
Usage (page): 0x1 (0xff00)
Bus type: 1 (USB)

Report Descriptor: (34 bytes)
0x06, 0x00, 0xff, 0x09, 0x01, 0xa1, 0x01, 0x09, 0x02, 0x15,
0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x40, 0x81, 0x02,
0x09, 0x03, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95,
0x40, 0x91, 0x02, 0xc0,

@mcuee mcuee added the Windows Related to Windows backend label Jun 16, 2023
@mcuee
Copy link
Member Author

mcuee commented Jun 16, 2023

Relevant discussion before, which fixed the reported Input Report number of reading bytes

PR #232 is for sure correct, since the original code wrongly assumes the buffer does not contain the report ID.

Probably similar patch is requrired for Output report.

The Windows behavior is not wrong per se, Windows HID driver indeed adds the report ID 0 (no report ID) as the first byte in the buffer. However the report ID 0 is not really send on the USB bus. Since HIDAPI is a cross-platform library, I think we should make Windows to behave the same as Linux and macOS. Therefore we should not report 65 bytes in the above example for Windows, but rather 64 bytes which are the number of bytes the device really received.

When there is repot ID, Windows will still require the first byte to be the report ID, which is transmitted on the USB Bus.

Idealy we should always report the number of bytes transmitted on the USB bus, no matter there is a report ID or not. In that case, we will get the same number of bytes, for HID Input report, Output report and Feature report.

@mcuee mcuee changed the title hidapi Windows reports one byter longer of HID Output Report written than Linux and macOS hidapi Windows reports one byter longer of HID Output Report and Feature Report Jun 16, 2023
@mcuee mcuee changed the title hidapi Windows reports one byter longer of HID Output Report and Feature Report hidapi Windows reports one byter longer for HID Output Report and Feature Report Jun 16, 2023
@mcuee
Copy link
Member Author

mcuee commented Jun 16, 2023

Test using Jan Axelson's generic HID example here.
http://janaxelson.com/hidpage.htm

Windows reported 3 for Feature report writing and reading, one byter longer than the real device (2 bytes Input report, Output Report and Feature report, no report IDs). And same for Output report, Windows reports 3 bytes written.

Only for Input report, Windows is correct to report 2 bytes have been read, thanks to PR #232.

WIndows also always requires to send report ID 0 for the Output report for devices without report ID (and this causes the extra one byte length reported for the write), whereas Linux does not.

//Class specific descriptor - HID 
// Defines 2-byte Input, Output, and Feature reports with vendor-defined data.

ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={
{
  	0x06, 0xA0, 0xFF,		// Usage page (vendor defined) 
  	0x09, 0x01,				// Usage ID (vendor defined)
  	0xA1, 0x01,				// Collection (application)

	// The Input report
       0x09, 0x03,     		// Usage ID - vendor defined
       0x15, 0x00,     		// Logical Minimum (0)
       0x26, 0xFF, 0x00,   	// Logical Maximum (255)
       0x75, 0x08,     		// Report Size (8 bits)
       0x95, 0x02,     		// Report Count (2 fields)
       0x81, 0x02,     		// Input (Data, Variable, Absolute)  

	// The Output report
       0x09, 0x04,     		// Usage ID - vendor defined
       0x15, 0x00,     		// Logical Minimum (0)
       0x26, 0xFF, 0x00,   	// Logical Maximum (255)
       0x75, 0x08,     		// Report Size (8 bits)
       0x95, 0x02,     		// Report Count (2 fields)
       0x91, 0x02,      	// Output (Data, Variable, Absolute)  

	// The Feature report
       0x09, 0x05,     		// Usage ID - vendor defined
       0x15, 0x00,     		// Logical Minimum (0)
       0x26, 0xFF, 0x00,   	// Logical Maximum (255)
       0x75, 0x08,			// Report Size (8 bits)
       0x95, 0x02, 			// Report Count	(2 fields)				
       0xB1, 0x02,     		// Feature (Data, Variable, Absolute)  

  	0xC0}					// end collection
};

PS C:\work\hid\hidapitester> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
 01 02 00
Closing device

PS C:\work\hid\hidapitester> .\hidapitester --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 3 bytes:
 01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
 02 00
Closing device

PS C:\work\hid\hidapitester> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 01 02
Closing device

But in this case, hidapitester under Linux has the same behavior in terms of feature report as Windows.

And we have already a commit similar to PR #232 for Feature report. So maybe the device really has a three bytes Feature report.

6fcb0bb

mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester  --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
 01 02 00
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester  --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
 01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
 01 02
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester  --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 01 02
Closing device

@mcuee
Copy link
Member Author

mcuee commented Jun 16, 2023

So I am pretty sure we need to fix number of bytes written Output Report under Windows, with all my test results in #478.

I am not so sure about Feature report yet since Linux behaves the same as Windows.

Current implementation under Windows. It mentions that Windows expects the number of bytes which are in the longest report (plus one for the report number) bytes even if the data is a report which is shorter than that.

I think we need to report the length minus one, at least for the case when there is no report ID.

int HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length)
{
	DWORD bytes_written = 0;
	int function_result = -1;
	BOOL res;
	BOOL overlapped = FALSE;

	unsigned char *buf;

	if (!data || !length) {
		register_string_error(dev, L"Zero buffer/length");
		return function_result;
	}

	register_string_error(dev, NULL);

	/* Make sure the right number of bytes are passed to WriteFile. Windows
	   expects the number of bytes which are in the _longest_ report (plus
	   one for the report number) bytes even if the data is a report
	   which is shorter than that. Windows gives us this value in
	   caps.OutputReportByteLength. If a user passes in fewer bytes than this,
	   use cached temporary buffer which is the proper size. */
	if (length >= dev->output_report_length) {
		/* The user passed the right number of bytes. Use the buffer as-is. */
		buf = (unsigned char *) data;
	} else {
		if (dev->write_buf == NULL)
			dev->write_buf = (unsigned char *) malloc(dev->output_report_length);
		buf = dev->write_buf;
		memcpy(buf, data, length);
		memset(buf + length, 0, dev->output_report_length - length);
		length = dev->output_report_length;
	}

	res = WriteFile(dev->device_handle, buf, (DWORD) length, NULL, &dev->write_ol);

	if (!res) {
		if (GetLastError() != ERROR_IO_PENDING) {
			/* WriteFile() failed. Return error. */
			register_winapi_error(dev, L"WriteFile");
			goto end_of_function;
		}
		overlapped = TRUE;
	}

	if (overlapped) {
		/* Wait for the transaction to complete. This makes
		   hid_write() synchronous. */
		res = WaitForSingleObject(dev->write_ol.hEvent, 1000);
		if (res != WAIT_OBJECT_0) {
			/* There was a Timeout. */
			register_winapi_error(dev, L"hid_write/WaitForSingleObject");
			goto end_of_function;
		}

		/* Get the result. */
		res = GetOverlappedResult(dev->device_handle, &dev->write_ol, &bytes_written, FALSE/*wait*/);
		if (res) {
			function_result = bytes_written;
		}
		else {
			/* The Write operation failed. */
			register_winapi_error(dev, L"hid_write/GetOverlappedResult");
			goto end_of_function;
		}
	}

end_of_function:
	return function_result;
}

@mcuee
Copy link
Member Author

mcuee commented Jun 16, 2023

@JoergAtGithub

I think you have very good understanding of the issue and the necessary test devices. Please take a look at this issue as well when you got the chance.

@mcuee
Copy link
Member Author

mcuee commented Jun 16, 2023

@Youw

I think for Feature report, Linux hidraw implemenation is kind of the same as Windows. So that probably explains why I see the same behavior under Windows and Linux, since we just report the buffer length (without minus one for the case when there is no repor ID) and not the data on the USB bus.

Commits 6fcb0bb is for sure correct, since the original code wrongly assues the buffer does not contain the Feature Report ID.

Now the question is whether we need to minus 1 for the case of no Report ID, to reflect the data on the USB Bus, and not the OS driver.

https://www.kernel.org/doc/Documentation/hid/hidraw.txt

HIDIOCSFEATURE(len): Send a Feature Report
This ioctl will send a feature report to the device. Per the HID
specification, feature reports are always sent using the control endpoint.
Set the first byte of the supplied buffer to the report number. For devices
which do not use numbered reports, set the first byte to 0. The report data
begins in the second byte. Make sure to set len accordingly, to one more
than the length of the report (to account for the report number).

HIDIOCGFEATURE(len): Get a Feature Report
This ioctl will request a feature report from the device using the control
endpoint. The first byte of the supplied buffer should be set to the report
number of the requested report. For devices which do not use numbered
reports, set the first byte to 0. The report will be returned starting at
the first byte of the buffer (ie: the report number is not returned).

@mcuee
Copy link
Member Author

mcuee commented Jun 16, 2023

As of now, hidapitester using hidapi-libusb (custom build) behaves pretty same as hidapi-hidraw. So that is a good thing.

mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester_libusb  --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
 01 02 00
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester_libusb  --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
 01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
 01 02
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester$ sudo ./hidapitester_libusb  --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 01 02
Closing device

@mcuee mcuee changed the title hidapi Windows reports one byter longer for HID Output Report and Feature Report hidapi Windows reports one byte more for HID Output Report and Feature Report Jun 16, 2023
@mcuee mcuee changed the title hidapi Windows reports one byte more for HID Output Report and Feature Report hidapi Windows reports one extra byte for HID Output Report and Feature Report Jun 16, 2023
@Youw
Copy link
Member

Youw commented Jun 16, 2023

  	@returns
  		This function returns the number of bytes read plus
  		one for the report ID (which is still in the first
  		byte), or -1 on error.
  		Call hid_error(dev) to get the failure reason.
  */
  int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);

As per HIDAPI documentation, we should include the 0 report ID in the return. Is it included?

@JoergAtGithub
Copy link
Contributor

JoergAtGithub commented Jun 16, 2023

0 is a placeholder and not a Report ID. This is because the HID class specification specifies "If a Report ID tag was used in the Report descriptor, all reports include a single byte ID prefix. If the Report ID tag was not used, all values are returned in a single report and a prefix ID is not included in that report." and further about the representation in the ReportDescriptor "Set Report ID to 0 (zero) if Report IDs are not used."

Therefore only values from 1 to 255 can be Report IDs. The value 0 can be used in the ReportDescriptor, to define that the reports do not contain a ReportID.

Windows implements it as specified in the class specification and adds the Report ID prefix byte only for devices that use Report IDs.

I know that we need to keep backward compatibility, but from a clean API I would expect a clear seperation between address and data in two arguments.

@mcuee
Copy link
Member Author

mcuee commented Jun 16, 2023

I know that we need to keep backward compatibility, but from a clean API I would expect a clear seperation between address and data in two arguments.

@JoergAtGithub

Just wondering what you mean by the above. Are you proposing some new APIs? I think that will be okay if there are real benefits. We can always keep the older APIs (and in the future to deprecate them in HIDAPI 1.x version if necessary).

@mcuee
Copy link
Member Author

mcuee commented Jun 17, 2023

Just tested under macOS (13.4, Mac Mini M1) and the behavior is the same as Linux. So only Windows is out for the Output Report.

mcuee@mcuees-Mac-mini hidapitester % ./hidapitester  --vidpid 0925:7001 --open -l 3 --send-output 0,1,2 --read-input       
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 01 02
Reading 3-byte input report 0, 250 msec timeout...read 2 bytes:
 01 02 00
Closing device

mcuee@mcuees-Mac-mini hidapitester % ./hidapitester  --vidpid 0925:7001 --open -l 2 --send-output 1,2 --read-input  
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
 01 02
Reading 2-byte input report 0, 250 msec timeout...read 2 bytes:
 01 02
Closing device

mcuee@mcuees-Mac-mini hidapitester % ./hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 0,1,2 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 01 02
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 01 02
Closing device

@Youw
Copy link
Member

Youw commented Jun 17, 2023

Just wondering what you mean by the above.

I believe that is a though process "out loud", i.e. "it is probably would be clean to have an API where we send the report ID as a separate argument and only the report body in the buffer".

I think that will not change anything in terms of this issue. We still have to fight with different drivers/OS implementation to match the behavior the way we want. And because as per current documentation - we should always count the report ID as part of the data returned - having it as a separate argument would essentially mean -1 to the result.
But it will complicate some backend implementations, e.g. hidraw, which expects a single buffer as well. And if report ID will be a separate argument - we would have to make an intermediate buffer/copy before we can pass it into the underlying API.

@JoergAtGithub
Copy link
Contributor

But pre-pending the 0 byte is also an operation, which requires intermediate buffer/copy.

@Youw
Copy link
Member

Youw commented Jun 17, 2023

That's exactly my point.
Having report ID at the beginning of the buffer - means not extra buffer "prepending" on HIDAPI side.

@JoergAtGithub
Copy link
Contributor

If you want to unify the behaviour between platforms you need either to prepend the 0 byte on Windows send functions for devices that don't use a Report ID or you need to remove the 0 byte for the other backends.
Because we have send and receive functions, there will be always a case, where you have to prepend the 0.
Performance wise I would say receiving reports is a more critical operation than sending.

@mcuee
Copy link
Member Author

mcuee commented Jun 17, 2023

That's exactly my point. Having report ID at the beginning of the buffer - means not extra buffer "prepending" on HIDAPI side.

There are two aspects of the issue.

1. Buffer handling -- platform specific.

I think the current codes already do that. For example, Windows codes have already done that, at least for Output Report and Feature Report (first byte of the report is the report ID, 0 when there is no report ID). And I believe all platforms are doing that for Feature report, based on my testing results.

I tend to believe the current codes are already fine in this aspect, i.e, we do not have issues in terms of getting thngs done (the device will receive the right data, host will also receive the right data).

2. Read/Write lentgh reporting

I think this is the issue now. It is not consistent.

Two ways of reporting -- now we are mixing the two.
a) lengh of the buffer used by underlying OS driver -- Feature Report (including the 0 byte for device without Report ID). Currently I believe there is no behaviour differences for Feature Report across platforms.

b) length of the data bytes appearing on the USB Bus -- Input Report (not including the 0 byte for device without Report ID). For Windows, this could be due to the way we handle Input Report differently from Output Report and Feature report. Currently I believe there is no behaviour differences for Input Report across platforms.

c) mix of the two ways -- Output Report (Windows always including the 0 byte for device without Report ID, other platforms may or may not including). So Windows may report one byte more than the other platforms or may report the same length as the other platforms. I think we should at least fix this one.

3. Potential Solution for Issue 2.
a) If we really want to unify all platforms in terms of the read/write length report mechanism, then I tend to htink Option b is the way to go for all three types of reports. But then it may require some jobs across all platforms and for Output and Feature reports..

b) If we just want to unify the behavior across platforms, then we only need to fix Output Report write length reporting to the user. I tend to think we only need to make Windows consistent with others. So only one platform (Windows) and one report type (Output Report) needs to be fixed.

@Youw
Copy link
Member

Youw commented Jun 17, 2023

If we just want to unify the behavior across platforms, then we only need to fix Output Report write length reporting to the user. I tend to think we only need to make Windows consistent with others. So only one platform (Windows) and one report type (Output Report) needs to be fixed.

+1

@mcuee mcuee changed the title hidapi Windows reports one extra byte for HID Output Report and Feature Report hidapi Windows reports one extra byte for HID Output Report compared to other platforms Jun 17, 2023
@mcuee
Copy link
Member Author

mcuee commented Jun 17, 2023

If we just want to unify the behavior across platforms, then we only need to fix Output Report write length reporting to the user. I tend to think we only need to make Windows consistent with others. So only one platform (Windows) and one report type (Output Report) needs to be fixed.

+1

Okay, I agree this is the way to go to keep backwards compatibillity and with minimum changes.

We may also need to improve the documentation as well.

@mcuee mcuee added the documentation Improvements or additions to documentation label Jun 17, 2023
@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

I will try to compare the Linux hidraw implementation versus Windows implementation.

Let's talk about INPUT Report first.

		/** @brief Read an Input report from a HID device with timeout.

			Input reports are returned
			to the host through the INTERRUPT IN endpoint. The first byte will
			contain the Report number if the device uses numbered reports.

			@ingroup API
			@param dev A device handle returned from hid_open().
			@param data A buffer to put the read data into.
			@param length The number of bytes to read. For devices with
				multiple reports, make sure to read an extra byte for
				the report number.
			@param milliseconds timeout in milliseconds or -1 for blocking wait.

			@returns
				This function returns the actual number of bytes read and
				-1 on error.
				Call hid_error(dev) to get the failure reason.
				If no packet was available to be read within
				the timeout period, this function returns 0.
		*/
		int HID_API_EXPORT HID_API_CALL hid_read_timeout(hid_device *dev, unsigned char *data, size_t length, int milliseconds);

		/** @brief Get a input report from a HID device.

			Since version 0.10.0, @ref HID_API_VERSION >= HID_API_MAKE_VERSION(0, 10, 0)

			Set the first byte of @p data[] to the Report ID of the
			report to be read. Make sure to allow space for this
			extra byte in @p data[]. Upon return, the first byte will
			still contain the Report ID, and the report data will
			start in data[1].

			@ingroup API
			@param dev A device handle returned from hid_open().
			@param data A buffer to put the read data into, including
				the Report ID. Set the first byte of @p data[] to the
				Report ID of the report to be read, or set it to zero
				if your device does not use numbered reports.
			@param length The number of bytes to read, including an
				extra byte for the report ID. The buffer can be longer
				than the actual report.

			@returns
				This function returns the number of bytes read plus
				one for the report ID (which is still in the first
				byte), or -1 on error.
				Call hid_error(dev) to get the failure reason.
		*/
		int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length);

One thing hidapitester does not test is to use hid_get_input_report to get input report using Control Transfer. It only uses hid_read_timeout. So this may hide the issues with regard to Input Report.

1) Linux hidraw and Input Report

Linux hidraw docuemtation shows there will be no discrepancies using read method in hid_read_timeout and using HIDIOCSINPUT ioctl in hid_get_input_report.

read method: For devices which do not use numbered reports, the report data will begin at the first byte.

HIDIOCGINPUT ioctl: For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer. However, this contradicts the HIDAPI documentation for hid_get_input_report.

2) Windows HID API and Input Report
Windows hid_read_timeout implementation uses ReadFile which should give the same behavior as above.

Windows hid_get_input_report uses IOCTL_HID_GET_INPUT_REPORT, which will add the report ID 0 for devices which do not use numbered reports (no Report ID)

So we will have a discrepancy here within the Windows implementations betwen using hid_read_timeout and hid_read`. But this seems to be consistent with the HIDAPI documentation. This is quite confusing

int HID_API_EXPORT HID_API_CALL hid_get_input_report(hid_device *dev, unsigned char *data, size_t length)
{
	/* We could use HidD_GetInputReport() instead, but it doesn't give us an actual length, unfortunately */
	return hid_get_report(dev, IOCTL_HID_GET_INPUT_REPORT, data, length);
}

By the way, Windows hid_get_report has the same behavior as the above hid_get_input_report. It is mainly used for Feature Report (which is consistent with other platforms) but it is not used by Windows hid_get_input_report implementations in the end. Actually we can use it for hid_get_input_report without the following codes which is correct for Feature report. Then it will be consistent with hid_read.

	/* When numbered reports aren't used,
	   bytes_returned seem to include only what is actually received from the device
	   (not including the first byte with 0, as an indication "no numbered reports"). */
	if (data[0] == 0x0) {
		bytes_returned++;
	}

Reference for Windows
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidclass/ni-hidclass-ioctl_hid_get_input_report

IOCTL_HID_GET_INPUT_REPORT IOCTL (hidclass.h)

Input buffer
The Parameters.DeviceIoControl.OutputBufferLength member specifies the size of a requester-allocated output buffer in bytes. The HID class driver uses this buffer to return an input report.

If the collection includes report IDs, the requester must set the first byte of the output buffer to a nonzero report ID. Otherwise, the requester must set the first byte of the output buffer to zero.

Input buffer length
The size of the buffer in bytes. The buffer must be large enough to hold the input report plus one additional byte that specifies a nonzero report ID. If report ID is not used, the ID value is zero.

Reference for Linux: https://docs.kernel.org/hid/hidraw.html

read()
read() will read a queued report received from the HID device. On USB devices, the reports read using read() are the reports sent from the device on the INTERRUPT IN endpoint. By default, read() will block until there is a report available to be read. read() can be made non-blocking, by passing the O_NONBLOCK flag to open(), or by setting the O_NONBLOCK flag using fcntl().

On a device which uses numbered reports, the first byte of the returned data will be the report number; the report data follows, beginning in the second byte. For devices which do not use numbered reports, the report data will begin at the first byte.

write()
The write() function will write a report to the device. For USB devices, if the device has an INTERRUPT OUT endpoint, the report will be sent on that endpoint. If it does not, the report will be sent over the control endpoint, using a SET_REPORT transfer.

The first byte of the buffer passed to write() should be set to the report number. If the device does not use numbered reports, the first byte should be set to 0. The report data itself should begin at the second byte.

HIDIOCSFEATURE(len):
Send a Feature Report

This ioctl will send a feature report to the device. Per the HID specification, feature reports are always sent using the control endpoint. Set the first byte of the supplied buffer to the report number. For devices which do not use numbered reports, set the first byte to 0. The report data begins in the second byte. Make sure to set len accordingly, to one more than the length of the report (to account for the report number).

HIDIOCGFEATURE(len):
Get a Feature Report

This ioctl will request a feature report from the device using the control endpoint. The first byte of the supplied buffer should be set to the report number of the requested report. For devices which do not use numbered reports, set the first byte to 0. The returned report buffer will contain the report number in the first byte, followed by the report data read from the device. For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer.

HIDIOCSINPUT(len):
Send an Input Report

This ioctl will send an input report to the device, using the control endpoint. In most cases, setting an input HID report on a device is meaningless and has no effect, but some devices may choose to use this to set or reset an initial state of a report. The format of the buffer issued with this report is identical to that of HIDIOCSFEATURE.

HIDIOCGINPUT(len):
Get an Input Report

This ioctl will request an input report from the device using the control endpoint. This is slower on most devices where a dedicated In endpoint exists for regular input reports, but allows the host to request the value of a specific report number. Typically, this is used to request the initial states of an input report of a device, before an application listens for normal reports via the regular device read() interface. The format of the buffer issued with this report is identical to that of HIDIOCGFEATURE.

HIDIOCSOUTPUT(len):
Send an Output Report

This ioctl will send an output report to the device, using the control endpoint. This is slower on most devices where a dedicated Out endpoint exists for regular output reports, but is added for completeness. Typically, this is used to set the initial states of an output report of a device, before an application sends updates via the regular device write() interface. The format of the buffer issued with this report is identical to that of HIDIOCSFEATURE.

HIDIOCGOUTPUT(len):
Get an Output Report

This ioctl will request an output report from the device using the control endpoint. Typically, this is used to retrieve the initial state of an output report of a device, before an application updates it as necessary either via a HIDIOCSOUTPUT request, or the regular device write() interface. The format of the buffer issued with this report is identical to that of HIDIOCGFEATURE.

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

For Feature Report:

		/** @brief Send a Feature report to the device.

			Feature reports are sent over the Control endpoint as a
			Set_Report transfer.  The first byte of @p data[] must
			contain the Report ID. For devices which only support a
			single report, this must be set to 0x0. The remaining bytes
			contain the report data. Since the Report ID is mandatory,
			calls to hid_send_feature_report() will always contain one
			more byte than the report contains. For example, if a hid
			report is 16 bytes long, 17 bytes must be passed to
			hid_send_feature_report(): the Report ID (or 0x0, for
			devices which do not use numbered reports), followed by the
			report data (16 bytes). In this example, the length passed
			in would be 17.

			@ingroup API
			@param dev A device handle returned from hid_open().
			@param data The data to send, including the report number as
				the first byte.
			@param length The length in bytes of the data to send, including
				the report number.

			@returns
				This function returns the actual number of bytes written and
				-1 on error.
				Call hid_error(dev) to get the failure reason.
		*/
		int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);

		/** @brief Get a feature report from a HID device.

			Set the first byte of @p data[] to the Report ID of the
			report to be read.  Make sure to allow space for this
			extra byte in @p data[]. Upon return, the first byte will
			still contain the Report ID, and the report data will
			start in data[1].

			@ingroup API
			@param dev A device handle returned from hid_open().
			@param data A buffer to put the read data into, including
				the Report ID. Set the first byte of @p data[] to the
				Report ID of the report to be read, or set it to zero
				if your device does not use numbered reports.
			@param length The number of bytes to read, including an
				extra byte for the report ID. The buffer can be longer
				than the actual report.

			@returns
				This function returns the number of bytes read plus
				one for the report ID (which is still in the first
				byte), or -1 on error.
				Call hid_error(dev) to get the failure reason.
		*/
		int HID_API_EXPORT HID_API_CALL hid_get_feature_report(hid_device *dev, unsigned char *data, size_t length);

1) Linux hidraw for Feature Report

Linux hidraw uses the following IOCTLs for Sending and Receiving Feature Report.

The Linux hidraw documentation seems to indicate that Linux HIDAPI hid_get_feature_report implementation will not match the documentation, but that does not seem to be the case from my testing results. Strange.

https://docs.kernel.org/hid/hidraw.html
HIDIOCSFEATURE(len):
Send a Feature Report

This ioctl will send a feature report to the device. Per the HID specification, feature reports are always sent using the control endpoint. Set the first byte of the supplied buffer to the report number. For devices which do not use numbered reports, set the first byte to 0. The report data begins in the second byte. Make sure to set len accordingly, to one more than the length of the report (to account for the report number).

HIDIOCGFEATURE(len):
Get a Feature Report

This ioctl will request a feature report from the device using the control endpoint. The first byte of the supplied buffer should be set to the report number of the requested report. For devices which do not use numbered reports, set the first byte to 0. The returned report buffer will contain the report number in the first byte, followed by the report data read from the device. For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer.

2) Windows:

From the documentations and testing results, Windows implementation matches the HIDAPI documentation.

hid_send_feature_report uses HidD_SetFeature function.
hid_get_feature_report uses IOCTL_HID_GET_FEATURE

3) Refererence for Windows

1) HidD_SetFeature function (hidsdi.h)
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidsdi/nf-hidsdi-hidd_setfeature

[in] ReportBuffer

Pointer to a caller-allocated feature report buffer that the caller uses to specify a HID report ID.

For more information about this parameter, see the Remarks section.

[in] ReportBufferLength

The size of the report buffer in bytes. The report buffer must be large enough to hold the feature report plus one additional byte that specifies a nonzero report ID. If report ID is not used, the ID value is zero.

2) IOCTL_HID_GET_FEATURE IOCTL (hidclass.h)

https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidclass/ni-hidclass-ioctl_hid_get_feature
IOCTL_HID_GET_FEATURE IOCTL (hidclass.h)

Input buffer
The Parameters.DeviceIoControl.OutputBufferLength member specifies the size, in bytes, of a requester-allocated output buffer. The HID class driver uses this buffer to return a feature report.

If the collection includes report IDs, the requester must set the first byte of the output buffer to a nonzero report ID. Otherwise, the requester must set the first byte of the output buffer to zero.

Input buffer length
The size of the buffer in bytes. The buffer must be large enough to hold the feature report plus one additional byte that specifies a nonzero report ID. If report ID is not used, the ID value is zero.

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

For Output Report:

		/** @brief Write an Output report to a HID device.

			The first byte of @p data[] must contain the Report ID. For
			devices which only support a single report, this must be set
			to 0x0. The remaining bytes contain the report data. Since
			the Report ID is mandatory, calls to hid_write() will always
			contain one more byte than the report contains. For example,
			if a hid report is 16 bytes long, 17 bytes must be passed to
			hid_write(), the Report ID (or 0x0, for devices with a
			single report), followed by the report data (16 bytes). In
			this example, the length passed in would be 17.

			hid_write() will send the data on the first OUT endpoint, if
			one exists. If it does not, it will send the data through
			the Control Endpoint (Endpoint 0).

			@ingroup API
			@param dev A device handle returned from hid_open().
			@param data The data to send, including the report number as
				the first byte.
			@param length The length in bytes of the data to send.

			@returns
				This function returns the actual number of bytes written and
				-1 on error.
				Call hid_error(dev) to get the failure reason.
		*/
		int  HID_API_EXPORT HID_API_CALL hid_write(hid_device *dev, const unsigned char *data, size_t length);

1) Linux hidraw implementations.

From the documentation it seems to be in line with the above documentation. However, in testing, it seems to support both uses cases -- to ignore the report ID 0 or not to ignore the report ID 0 for devices without Report ID.

https://docs.kernel.org/hid/hidraw.html
write()
The write() function will write a report to the device. For USB devices, if the device has an INTERRUPT OUT endpoint, the report will be sent on that endpoint. If it does not, the report will be sent over the control endpoint, using a SET_REPORT transfer.

The first byte of the buffer passed to write() should be set to the report number. If the device does not use numbered reports, the first byte should be set to 0. The report data itself should begin at the second byte.

2) Windows implementation

Even though I mentioned that Windows may report one more byte than the other platforms, I tend to think it is actually according to the HIDAPI documentation.

From the comments: using Windows WriteFile API.

/* Make sure the right number of bytes are passed to WriteFile. Windows
	   expects the number of bytes which are in the _longest_ report (plus
	   one for the report number) bytes even if the data is a report
	   which is shorter than that. Windows gives us this value in
	   caps.OutputReportByteLength. If a user passes in fewer bytes than this,
	   use cached temporary buffer which is the proper size. */

Take note we do not use either HidD_SetOutputReport function or IOCTL_HID_SET_OUTPUT_REPORT due to their limitations. But they will also have the same behaviors.
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidsdi/nf-hidsdi-hidd_setoutputreport
https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/hidclass/ni-hidclass-ioctl_hid_set_output_report

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

Summary:

It seems to me HIDAPI Windows implementation is actually consistent with the HIDAPI documentation, even though the behavior seems to be a bit different from the other platforms, at least for the Output Reports.

To do:

  1. To test hid_get_input_report

I will try to use a modified version of hidapitester.

  1. To understand more about hidraw implementation to see if I can figure out the discrepancies between test results and hidraw documentation.

  2. To check HIDAPI libusb implementaiton -- I believe it matches the hidraw implementation based on testing results.

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

To understand more about hidraw implementation to see if I can figure out the discrepancies between test results and hidraw documentation.

My conclusion: I am guessing that Linux hidraw documentations on HIDIOCGFEATURE and HIDIOCGINPUT are wrong. And there are no issues in HIDAPI Linux hidraw implementations. This will explain why Linux hidraw is consistent with the Windows implementation in testing results, for both Input Report and Feature Report.

  1. Existing discussions about Feature Report.

Confusions about hid_get_feature_report
#229 (comment)
liquidctl/liquidctl#312

  1. I am guessing that It could be that Linux hidraw HIDIOCGFEATURE documentation is wrong and it actually will add report ID 0 for devices without Report ID. If that is the case, then it can explain why I get the same results from hidraw and libusb under Linux.

From Linux kernel source code, I think indeed this is the case.

https://github.com/torvalds/linux/blob/master/drivers/hid/hidraw.c

/*
 * This function performs a Get_Report transfer over the control endpoint
 * per section 7.2.1 of the HID specification, version 1.1.  The first byte
 * of buffer is the report number to request, or 0x0 if the device does not
 * use numbered reports. The report_type parameter can be HID_FEATURE_REPORT
 * or HID_INPUT_REPORT.
 */
static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type)
...

				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) {
					int len = _IOC_SIZE(cmd);
					ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT);
					break;
				}
...
				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGINPUT(0))) {
					int len = _IOC_SIZE(cmd);
					ret = hidraw_get_report(file, user_arg, len, HID_INPUT_REPORT);
					break;
				}
...
				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGOUTPUT(0))) {
					int len = _IOC_SIZE(cmd);
					ret = hidraw_get_report(file, user_arg, len, HID_OUTPUT_REPORT);
					break;
				}
  1. I am also guessing that Linux hidraw HIDIOCGINPUT documentation is also wrong, from the above source codes, it actually will add report ID 0 for devices without Report ID.

  2. The following are for references about writing Output report and Feature Report. No documentation issues from hidraw in this case.

/*
 * The first byte of the report buffer is expected to be a report number.
 */
static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type)
...
				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) {
					int len = _IOC_SIZE(cmd);
					ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT);
					break;
				}
...
				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSINPUT(0))) {
					int len = _IOC_SIZE(cmd);
					ret = hidraw_send_report(file, user_arg, len, HID_INPUT_REPORT);
					break;
				}
...
				if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSOUTPUT(0))) {
					int len = _IOC_SIZE(cmd);
					ret = hidraw_send_report(file, user_arg, len, HID_OUTPUT_REPORT);
					break;
				}

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

  1. To test hid_get_input_report

I will try to use a modified version of hidapitester.

Windows testing results with device with Report ID.

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42,0x43 --read-input 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 02 41 42
Reading 3-byte input report 1, 2000 msec timeout...read 3 bytes:
 01 41 42
Closing device

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42,0x43 --read-input-alt 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 02 41 42
Reading 3-byte input report, report_id 1...
read 3 bytes:
 01 41 42
Closing device

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 3,0x31,0x32 --read-feature 4
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 03 31 32
Reading 3-byte feature report, report_id 4...read 3 bytes:
 04 31 32
Closing device

Windows testing results with device without numbered Report ID.
Unfortunately the added command --read-input-alt does not seem to work with Device without Report ID.

Windows must send report ID 0 for Output Report. If not, it will not work as expected.
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 3 bytes: (automatically adding report ID 0)
 41 42
Reading 2-byte input report 0, 2000 msec timeout...read 2 bytes:
 42 00
Closing device

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 41 42
Reading 3-byte input report 0, 2000 msec timeout...read 2 bytes:
 41 42 00
Closing device

Windows must send report ID 0 for Output Report. If not, it will not work as expected.
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 3 bytes: (automatically adding report ID 0)
 41 42
Reading 2-byte input report, report_id 0...
read -1 bytes: (must read 3 bytes)
Closing device

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 41 42
Reading 3-byte input report, report_id 0...
read 3 bytes:
 00 00 00 (data not correct)
Closing device

Windows must send report ID 0 for Feature Report. If not, it will not work as expected.
PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 2 --send-feature 0x31.0x32 --read-feature 0

Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote 2 bytes: (adding 0 and then discard 0x32)
 31 00 
Reading 2-byte feature report, report_id 0...read -1 bytes: (must read 3 bytes)
 00 00
Closing device

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 3 --send-feature 0,0x32,0x33 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 32 33
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 32 33
Closing device

My modification:

diff --git a/hidapitester.c b/hidapitester.c
index de9d268..64f7b03 100644
--- a/hidapitester.c
+++ b/hidapitester.c
@@ -40,6 +40,7 @@ static void print_usage(char *myname)
 "  --read-feature <reportId>   Read Feature report (w/ reportId, 0 if unused) \n"
 "  --send-output <datalist>    Send Ouput report to device \n"
 "  --read-input [reportId]     Read Input report (w/ opt. reportId, if unused)\n"
+"  --read-input-alt [reportId] Read Input report using hidapi hid_get_input_report (w/ opt. reportId, if unused)\n"
 "  --read-input-forever [rId]  Read Input reports in a loop forever \n"
 "  --length <len>, -l <len>    Set buffer length in bytes of report to send/read\n"
 "  --timeout <msecs>           Timeout in millisecs to wait for input reads \n"
@@ -86,6 +87,7 @@ enum {
     CMD_SEND_OUTPUT,
     CMD_SEND_FEATURE,
     CMD_READ_INPUT,
+	CMD_READ_INPUT_ALT,
     CMD_READ_FEATURE,
     CMD_READ_INPUT_FOREVER,
 };
@@ -206,10 +208,11 @@ int main(int argc, char* argv[])
          {"send-output",  required_argument, &cmd,   CMD_SEND_OUTPUT},
          {"send-out",     required_argument, &cmd,   CMD_SEND_OUTPUT},
          {"send-feature", required_argument, &cmd,   CMD_SEND_FEATURE},
-         {"read-input",   optional_argument, &cmd,   CMD_READ_INPUT},
-         {"read-in",      optional_argument, &cmd,   CMD_READ_INPUT},
+         {"read-input",   required_argument, &cmd,   CMD_READ_INPUT},
+		 {"read-input-alt",   required_argument, &cmd,   CMD_READ_INPUT_ALT},
+         {"read-in",      required_argument, &cmd,   CMD_READ_INPUT},
          {"read-feature", required_argument, &cmd,   CMD_READ_FEATURE},
-         {"read-input-forever",  optional_argument, &cmd,   CMD_READ_INPUT_FOREVER},
+         {"read-input-forever",  required_argument, &cmd,   CMD_READ_INPUT_FOREVER},
          {NULL,0,0,0}
         };
     char* shortopts = "vht:l:qb:";
@@ -396,6 +399,32 @@ int main(int argc, char* argv[])
                     }
                 } while( cmd == CMD_READ_INPUT_FOREVER );
             }
+			
+			else if( cmd == CMD_READ_INPUT_ALT ) {
+
+                if( !dev ) {
+                    msg("Error on read: no device opened.\n"); break;
+                }
+                if( !buflen) {
+                    msg("Error on read: buffer length is 0. Use --len to specify.\n");
+                    break;
+                }
+                uint8_t report_id = (optarg) ? strtol(optarg,NULL,10) : 0;
+                buf[0] = report_id;
+                msg("Reading %d-byte input report, report_id %d... \n",buflen, report_id);
+                
+				res = hid_get_input_report(dev, buf, buflen);
+				msg("read %d bytes:\n", res);
+                if( res > 0 ) {
+                    printbuf(buf,buflen, print_base, print_width);
+                    memset(buf,0,buflen);  // clear it out
+                }
+                else if( res == -1 )  { // removed device
+                    cmd = CMD_CLOSE;
+                     break;
+                }
+            }
+			
             else if( cmd == CMD_READ_FEATURE ) {
 
                 if( !dev ) {

hidapitester_mod.zip

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

Linux hidraw test results

1) For devices with numbered report ID

Results are consistent with Windows.

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 02 41 42
Reading 3-byte input report 1, 2000 msec timeout...read 3 bytes:
 01 41 42
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input-alt 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 02 41 42
Reading 3-byte input report, report_id 1... 
read 3 bytes:
 01 41 42
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 3,0x41,0x42 --read-feature 4
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 03 41 42
Reading 3-byte feature report, report_id 4...read 3 bytes:
 04 41 42
Closing device

2) For devices without numbered report ID.

Result are not always consistent with Windows. But if we follow HIDAPI documentatation exactly, it seems to be consistent.

(For Output Report, report ID 0 can be sent or not sent, both cases are working, just there is one byte difference in the number of bytes written)

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
 41 42
Reading 2-byte input report 0, 2000 msec timeout...read 2 bytes:
 41 42
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 41 42
Reading 3-byte input report 0, 2000 msec timeout...read 2 bytes:
 41 42 00
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
 41 42
Reading 2-byte input report, report_id 0... 
read 2 bytes:
 00 00 (data not correct)
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 41 42
Reading 3-byte input report, report_id 0... 
read 3 bytes:
 00 00 00 (data not correct)
Closing device

(For Feature Report, report ID 0 must be sent, if not will get error)
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 0,0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 31 32
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 31 32
Closing device

(For Feature Report, report ID 0 must be sent, if not will get error)
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-feature 0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote -1 bytes:
 31 32
Reading 2-byte feature report, report_id 0...read 2 bytes:
 00 31
Closing device

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

To check HIDAPI libusb implementaiton -- I believe it matches the hidraw implementation based on testing results.

Linux libusb test results

1) For devices with numbered report ID

Results are consistent with Windows

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 02 41 42
Reading 3-byte input report 1, 2000 msec timeout...read 3 bytes:
 01 41 42
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 2,0x41,0x42 --read-input-alt 1
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 02 41 42
Reading 3-byte input report, report_id 1... 
read 3 bytes:
 01 41 42
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 3,0x41,0x42 --read-feature 4
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 03 41 42
Reading 3-byte feature report, report_id 4...read 3 bytes:
 04 41 42
Closing device

2) For devices without numbered report ID.
Results are consistent with hidraw backend.

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 0,0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 31 32
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 31 32
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-feature 0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote -1 bytes:
 31 32
Reading 2-byte feature report, report_id 0...read 2 bytes:
 00 31
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-output 0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 2-bytes...wrote 2 bytes:
 41 42
Reading 2-byte input report, report_id 0... 
read 2 bytes:
 00 00 (data not correct)
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-output 0,0x41,0x42 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 3-bytes...wrote 3 bytes:
 00 41 42
Reading 3-byte input report, report_id 0... 
read 3 bytes:
 00 00 00 (data  not correct)
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 3 --timeout 2000 --send-feature 0,0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 3-byte feature report...wrote 3 bytes:
 00 31 32
Reading 3-byte feature report, report_id 0...read 3 bytes:
 00 31 32
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 2 --timeout 2000 --send-feature 0x31,0x32 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 2-byte feature report...wrote -1 bytes:
 31 32
Reading 2-byte feature report, report_id 0...read 2 bytes:
 00 31
Closing device

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

@Youw and @todbot

I need some help on using hid_get_input_report with hidapitester. I think I need to implement some types of time out. Currently it returns (I believe) the correct number of bytes reading but the data are not correct (all 0).

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

@Youw

Somehow based on the test results, if I follow exactly the existing HIDAPI documentation, there are no issues, Windows implementations are consistent with Linux hidraw and Linux libusb implementations.

It is just that Linux hidraw and libusb implementations are more flexible in terms of output reports.

So in the end, maybe there is nothing to fix.

And I think it is not crticial that hidapi is a bit lax in terms of reporting to the user number of bytes written, as long as the device gets the right data.

But this still pending the confirmation from the above hid_get_input_report.

Edit: there is one thing to fix about documentation for hid_send_feature_report.

		/** @brief Send a Feature report to the device.

			Feature reports are sent over the Control endpoint as a
			Set_Report transfer.  The first byte of @p data[] must
			contain the Report ID. For devices which only support a
			single report, this must be set to 0x0. The remaining bytes
			contain the report data. Since the Report ID is mandatory,
			calls to hid_send_feature_report() will always contain one
			more byte than the report contains. For example, if a hid
			report is 16 bytes long, 17 bytes must be passed to
			hid_send_feature_report(): the Report ID (or 0x0, for
			devices which do not use numbered reports), followed by the
			report data (16 bytes). In this example, the length passed
			in would be 17.

			@ingroup API
			@param dev A device handle returned from hid_open().
			@param data The data to send, including the report number as
				the first byte.
			@param length The length in bytes of the data to send, including
				the report number.

			@returns
				**This function returns the actual number of bytes written** and
				-1 on error.
				Call hid_error(dev) to get the failure reason.
		*/
		int HID_API_EXPORT HID_API_CALL hid_send_feature_report(hid_device *dev, const unsigned char *data, size_t length);

The last part is not correct. It should be changed to the following.

			@returns
				This function returns the actual number of bytes written plus
				one for the report ID (which is still in the first byte), and
				-1 on error.
				Call hid_error(dev) to get the failure reason.

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

@todbot and @JoergAtGithub

If possible, please test on your end to see if you come to the same conclusion as I or not. Thanks.

My conclusion is based on my test devices and it could be totally wrong if the firmware has issues.

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

@jonasmalacofilho

Confusions about hid_get_feature_report
#229 (comment)
liquidctl/liquidctl#312

You mentioned the following.

However, the same text appears in hid_get_feature_report which, in turn, seems to have conflicting implementations:

  • the libusb implementation appears to follow the interpretation above;
  • but the hidraw implementation simply wraps HIDIOCGFEATURE, which is documented to have a different behavior: "For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer."

If my conclusion is correct that Linux hidraw documentation about HIDIOCGFEATURE is incorrect, then your confusion will be cleared.

Now we need to confirm with Linux HID developer side.

Since you are also very familiar with hidapi and hidraw, please check on your side as well under Linux. Thanks.

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

@Youw and @todbot

I need some help on using hid_get_input_report with hidapitester. I think I need to implement some types of time out. Currently it returns (I believe) the correct number of bytes reading but the data are not correct (all 0).

I think my modification is working even though it is a quick and dirty fix. It turns out I did not modify Jan Axeson's original HID FW correctly for devices without numbered report ID. I have fixed that for report length <=63 Bytes. I still have one issue with the FW for Report length = 64 Bytes but I will try to fix that later.

Here are the results under Windows and I can see that Windows implementation is as per the documentation, using the FW without numbered report ID and report length = 63 Bytes.

We can see that hid_read_timeout gets 63 bytes and hid_get_input_report gets 64 bytes under Windows, which is as per the HIDAPI documentation. Whether that is a good thing or not is another story.

ROM struct{BYTE report[HID_RPT01_SIZE];}hid_rpt01={
{
  	0x06, 0xA0, 0xFF,		// Usage page (vendor defined) 
  	0x09, 0x01,				// Usage ID (vendor defined)
  	0xA1, 0x01,				// Collection (application)

	// The Input report
       0x09, 0x03,     		// Usage ID - vendor defined
       0x15, 0x00,     		// Logical Minimum (0)
       0x26, 0xFF, 0x00,   	// Logical Maximum (255)
       0x75, 0x08,     		// Report Size (8 bits)
       0x95, 0x3f,     		// Report Count (63 fields)
       0x81, 0x02,     		// Input (Data, Variable, Absolute)  

	// The Output report
       0x09, 0x04,     		// Usage ID - vendor defined
       0x15, 0x00,     		// Logical Minimum (0)
       0x26, 0xFF, 0x00,   	// Logical Maximum (255)
       0x75, 0x08,     		// Report Size (8 bits)
       0x95, 0x3f,     		// Report Count (63 fields)
       0x91, 0x02,      	// Output (Data, Variable, Absolute)  

	// The Feature report
       0x09, 0x05,     		// Usage ID - vendor defined
       0x15, 0x00,     		// Logical Minimum (0)
       0x26, 0xFF, 0x00,   	// Logical Maximum (255)
       0x75, 0x08,			// Report Size (8 bits)
       0x95, 0x3f, 			// Report Count	(63 fields)				
       0xB1, 0x02,     		// Feature (Data, Variable, Absolute)  

  	0xC0}					// end collection
};

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report 0, 4000 msec timeout...read 63 bytes:
 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20
 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 00
Closing device

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report, report_id 0...
read 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device

PS C:\work\hid\hidapitester_mod> .\hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-feature 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 64-byte feature report...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte feature report, report_id 0...read 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E B1 0B 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

Linux hidraw testing results are the same as Windows if I use the same commands (as per HIDAPI documentation).

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report 0, 4000 msec timeout...read 63 bytes:
 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20
 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 00
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report, report_id 0... 
read 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-feature 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 64-byte feature report...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte feature report, report_id 0...read 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E C6 0F 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

Linux libusb testing results are the same as Windows if I use the same commands (as per HIDAPI documentation).

mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report 0, 4000 msec timeout...read 63 bytes:
 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20
 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 00
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-output 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-input-alt 0
Opening device, vid/pid: 0x0925/0x7001
Writing output report of 64-bytes...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte input report, report_id 0... 
read 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device
mcuee@UbuntuSwift3:~/build/hid/hidapitester_mod$ sudo ./hidapitester_libusb --vidpid 0925:7001 --open -l 64 --timeout 4000 --send-feature 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63 --read-feature 0
Opening device, vid/pid: 0x0925/0x7001
Writing 64-byte feature report...wrote 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Reading 64-byte feature report, report_id 0...read 64 bytes:
 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E ED 0F 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F
 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F
Closing device

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

@Youw

In the end, if my testing results are correct, then there is only one thing to fix about documentation for hid_send_feature_report.

It should be changed to the following.

@returns
				This function returns the actual number of bytes written plus
				one for the report ID (which is still in the first byte), and
				-1 on error.
				Call hid_error(dev) to get the failure reason.

One thing I would like to do is to check with linux-input mailing list to confirm that Linux hidraw documentation is not correct for the two IOCTLs: HIDIOCGFEATURE and HIDIOCGINPUT.

https://docs.kernel.org/hid/hidraw.html
https://github.com/torvalds/linux/blob/master/Documentation/hid/hidraw.rst

HIDIOCGFEATURE(len):
Get a Feature Report
This ioctl will request a feature report from the device using the control endpoint. The first byte of the supplied buffer should be set to the report number of the requested report. For devices which do not use numbered reports, set the first byte to 0. The returned report buffer will contain the report number in the first byte, followed by the report data read from the device. For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer.

I beleive this shoudl be:
For devices which do not use numbered reports, the report data will begin at the first byte of the returned buffer.

HIDIOCGINPUT(len):
Get an Input Report
This ioctl will request an input report from the device using the control endpoint. This is slower on most devices where a dedicated In endpoint exists for regular input reports, but allows the host to request the value of a specific report number. Typically, this is used to request the initial states of an input report of a device, before an application listens for normal reports via the regular device read() interface. The format of the buffer issued with this report is identical to that of HIDIOCGFEATURE.

@Youw
Copy link
Member

Youw commented Jun 18, 2023

fix about documentation for hid_send_feature_report.
It should be changed to the following.

That I agree, it will be way more consistent overall.


I'd make one general statement: from a good API/architecture point of view: if we're passing a buffer (regardless of its nature) and expect it to be read or written to - it is best if API function (the return value) gives that buffer size (from the start) in case of success.
I.e.: when the report IDs are not used and the report size is 4 bytes, user code still have to prepare a buffer of 5 bytes. And in case of success - it is best to return 5 (even if there been only 4 bytes transfered over the transport bus - not sure if that's the case for HID, but that's not my point), to confirm that the implementation has accounted for all bytes in the buffer. It also makes all validations (proxy/wrappers if needed) simpler and device-independent.

@mcuee
Copy link
Member Author

mcuee commented Jun 18, 2023

I'd make one general statement: from a good API/architecture point of view: if we're passing a buffer (regardless of its nature) and expect it to be read or written to - it is best if API function (the return value) gives that buffer size (from the start) in case of success.
I.e.: when the report IDs are not used and the report size is 4 bytes, user code still have to prepare a buffer of 5 bytes. And in case of success - it is best to return 5 (even if there been only 4 bytes transfered over the transport bus - not sure if that's the case for HID, but that's not my point), to confirm that the implementation has accounted for all bytes in the buffer. It also makes all validations (proxy/wrappers if needed) simpler and device-independent.

@Youw

Great. I have actually similar thoughts when I wrote the following in the above comment.
We can see that hid_read_timeout gets 63 bytes and hid_get_input_report gets 64 bytes under Windows, which is as per the HIDAPI documentation. Whether that is a good thing or not is another story.

If we look at the testing results, only hid_read_timeout and hid_read are not following your thoughts. We can not remove the two due to backwards compatibility reason. However, we can create two new functions which add the fake Report ID 0 to the buffer and then it will be in line with hid_get_input_report. Maybe we can call them hid_read_timeout_1 and hid_read_1. Sorry for the lame names. :-)

@mcuee
Copy link
Member Author

mcuee commented Jun 19, 2023

One thing I would like to do is to check with linux-input mailing list to confirm that Linux hidraw documentation is not correct for the two IOCTLs: HIDIOCGFEATURE and HIDIOCGINPUT.

https://docs.kernel.org/hid/hidraw.html
https://github.com/torvalds/linux/blob/master/Documentation/hid/hidraw.rst

Question asked in linux-input mailing list.
https://marc.info/?l=linux-input&m=168715494222601&w=3

@Youw
Copy link
Member

Youw commented Jun 19, 2023

only hid_read_timeout and hid_read are not following your thoughts

I don't think we should change the behavior of that one either.
Maybe from the usage perspective it is a bit less convenient, maybe the API is a bit inconsistent compared to other API calls, but overall it follows the general statement noted above - the return value gives the number of bytes written to the buffer, e.g. there would be no cases where return 4 could mean that the bytes [1-4] are written - it is always [0-3], i.e. the first 4 bytes in the buffer.
As for the handling of the report and detecting if the first byte is the report ID or not - user application has to know its device anyway, to interpret the recport correctly. Either dummy 0 report ID is prepended at the beginning or not (for devices that do not use numbered reports) - that doesn't change things drastically.

And, of course, breaking backward compativility is something we should to avoid as much as we can.

@mcuee
Copy link
Member Author

mcuee commented Jun 19, 2023

Modified testing code from the following Linux kernel example using hidraw for the above test device
https://github.com/torvalds/linux/blob/master/samples/hidraw/hid-example.c

// SPDX-License-Identifier: GPL-2.0
/*
 * Hidraw Userspace Example
 *
 * Copyright (c) 2010 Alan Ott <alan@signal11.us>
 * Copyright (c) 2010 Signal 11 Software
 *
 * The code may be used by anyone for any purpose,
 * and can serve as a starting point for developing
 * applications using hidraw.
 */

/* Linux */
#include <linux/types.h>
#include <linux/input.h>
#include <linux/hidraw.h>

/*
 * Ugly hack to work around failing compilation on systems that don't
 * yet populate new version of hidraw.h to userspace.
 */
#ifndef HIDIOCSFEATURE
#warning Please have your distro update the userspace kernel headers
#define HIDIOCSFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
#define HIDIOCGFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
#endif

/* Unix */
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

/* C */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

const char *bus_str(int bus);

int main(int argc, char **argv)
{
	int fd;
	int i, res, desc_size = 0;
	char buf[64];
	struct hidraw_report_descriptor rpt_desc;
	struct hidraw_devinfo info;
	char *device = "/dev/hidraw1";

	if (argc > 1)
		device = argv[1];

	/* Open the Device with non-blocking reads. In real life,
	   don't use a hard coded path; use libudev instead. */
	fd = open(device, O_RDWR|O_NONBLOCK);

	if (fd < 0) {
		perror("Unable to open device");
		return 1;
	}

	memset(&rpt_desc, 0x0, sizeof(rpt_desc));
	memset(&info, 0x0, sizeof(info));
	memset(buf, 0x0, sizeof(buf));

	/* Get Report Descriptor Size */
	res = ioctl(fd, HIDIOCGRDESCSIZE, &desc_size);
	if (res < 0)
		perror("HIDIOCGRDESCSIZE");
	else
		printf("Report Descriptor Size: %d\n", desc_size);

	/* Get Report Descriptor */
	rpt_desc.size = desc_size;
	res = ioctl(fd, HIDIOCGRDESC, &rpt_desc);
	if (res < 0) {
		perror("HIDIOCGRDESC");
	} else {
		printf("Report Descriptor:\n");
		for (i = 0; i < rpt_desc.size; i++)
			printf("%hhx ", rpt_desc.value[i]);
		puts("\n");
	}

	/* Get Raw Name */
	res = ioctl(fd, HIDIOCGRAWNAME(256), buf);
	if (res < 0)
		perror("HIDIOCGRAWNAME");
	else
		printf("Raw Name: %s\n", buf);

	/* Get Physical Location */
	res = ioctl(fd, HIDIOCGRAWPHYS(256), buf);
	if (res < 0)
		perror("HIDIOCGRAWPHYS");
	else
		printf("Raw Phys: %s\n", buf);

	/* Get Raw Info */
	res = ioctl(fd, HIDIOCGRAWINFO, &info);
	if (res < 0) {
		perror("HIDIOCGRAWINFO");
	} else {
		printf("Raw Info:\n");
		printf("\tbustype: %d (%s)\n",
			info.bustype, bus_str(info.bustype));
		printf("\tvendor: 0x%04hx\n", info.vendor);
		printf("\tproduct: 0x%04hx\n", info.product);
	}

	/* Set Feature */
	buf[0] = 0x0; /* Report Number */
	for (i = 1; i < 64; i++)
		buf[i] = 0x40 + i;

	res = ioctl(fd, HIDIOCSFEATURE(64), buf);
	if (res < 0)
		perror("HIDIOCSFEATURE");
	else
		printf("ioctl HIDIOCSFEATURE returned: %d\n", res);

	/* Get Feature */
	buf[0] = 0x0; /* Report Number */
	res = ioctl(fd, HIDIOCGFEATURE(64), buf);
	if (res < 0) {
		perror("HIDIOCGFEATURE");
	} else {
		printf("ioctl HIDIOCGFEATURE returned: %d\n", res);
		printf("Report data:\n\t");
		for (i = 0; i < res; i++)
			printf("%hhx ", buf[i]);
		puts("\n");
	}

	/* Send a Report to the Device */
	buf[0] = 0x0; /* Report Number */
	for (i = 1; i < 64; i++)
		buf[i] = 0x20 + i;
	
	res = write(fd, buf, 64);
	if (res < 0) {
		printf("Error: %d\n", errno);
		perror("write");
	} else {
		printf("write() wrote %d bytes\n", res);
	}
	//sleep(1);
	/* Get a report from the device */
	res = read(fd, buf, 64);
	if (res < 0) {
		perror("read");
	} else {
		printf("read() read %d bytes:\n\t", res);
		for (i = 0; i < res; i++)
			printf("%hhx ", buf[i]);
		puts("\n");
	}
	close(fd);
	return 0;
}

const char *
bus_str(int bus)
{
	switch (bus) {
	case BUS_USB:
		return "USB";
		break;
	case BUS_HIL:
		return "HIL";
		break;
	case BUS_BLUETOOTH:
		return "Bluetooth";
		break;
	case BUS_VIRTUAL:
		return "Virtual";
		break;
	default:
		return "Other";
		break;
	}
}

mcuee@UbuntuSwift3:~/build/hid/hidraw$ gcc -o myhid-example myhid-example.c 

mcuee@UbuntuSwift3:~/build/hid/hidraw$ sudo ./myhid-example 
Report Descriptor Size: 47
Report Descriptor:
6 a0 ff 9 1 a1 1 9 3 15 0 26 ff 0 75 8 95 3f 81 2 9 4 15 0 26 ff 0 75 8 95 3f 91 2 9 5 15 0 26 ff 0 75 8 95 3f b1 2 c0 

Raw Name: Microchip Technology Inc. Generic HID
Raw Phys: usb-0000:00:14.0-1/input0
Raw Info:
	bustype: 3 (USB)
	vendor: 0x0925
	product: 0x7001
ioctl HIDIOCSFEATURE returned: 64
ioctl HIDIOCGFEATURE returned: 64
Report data:
	0 41 42 43 44 45 46 47 48 14 4 18 4 4d 72 31 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 60 61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a 7b 7c 7d 7e 7f 

write() wrote 64 bytes
read() read 63 bytes:
	21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f 40 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a 5b 5c 5d 5e 5f 

@mcuee mcuee changed the title hidapi Windows reports one extra byte for HID Output Report compared to other platforms hidapi documentation about number of bytes written and read Jun 19, 2023
@mcuee mcuee removed the Windows Related to Windows backend label Jun 19, 2023
@mcuee
Copy link
Member Author

mcuee commented Jun 19, 2023

Moreover the kernel codes here seem to prove that Linux documentation on hidraw
is wrong for both HIDIOCGFEATURE and HIDIOCGINPUT.

https://github.com/torvalds/linux/blob/master/include/uapi/linux/hidraw.h

/* The first byte of SFEATURE and GFEATURE is the report number */
#define HIDIOCSFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x06, len)
#define HIDIOCGFEATURE(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x07, len)
#define HIDIOCGRAWUNIQ(len)     _IOC(_IOC_READ, 'H', 0x08, len)
/* The first byte of SINPUT and GINPUT is the report number */
#define HIDIOCSINPUT(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x09, len)
#define HIDIOCGINPUT(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0A, len)
/* The first byte of SOUTPUT and GOUTPUT is the report number */
#define HIDIOCSOUTPUT(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0B, len)
#define HIDIOCGOUTPUT(len)    _IOC(_IOC_WRITE|_IOC_READ, 'H', 0x0C, len)

tormodvolden added a commit to libusb/libusb that referenced this issue Jun 21, 2023
…out report IDs"

This reverts commit 69e4ee6.

In the commit (from #1217) it was
wrongly assumed what the transfer length actually includes, and the
change caused wrong values for Output Report and Feature Report.

See also #1285 where the regression
was first analyzed.

More discussion about lengths reported from HIDAPI point of view:
libusb/hidapi#589

References #1217
References #1285
@mcuee
Copy link
Member Author

mcuee commented Jun 22, 2023

One thing I would like to do is to check with linux-input mailing list to confirm that Linux hidraw documentation is not correct for the two IOCTLs: HIDIOCGFEATURE and HIDIOCGINPUT.
https://docs.kernel.org/hid/hidraw.html
https://github.com/torvalds/linux/blob/master/Documentation/hid/hidraw.rst

Question asked in linux-input mailing list. https://marc.info/?l=linux-input&m=168715494222601&w=3

https://lore.kernel.org/linux-input/1f24c83c-fe36-d2ab-c755-e83fc6a265eb@redhat.com/T/#m458a231a4f3c9eb91553656b9d7d50d86cacd4be

Answer from the linux-input core maintainer.

++++++++++++++++++++++
Yep, this is wrong.

This should be read:

The returned report buffer will contain the report number in the first 
byte or 0 when the device doesn't use numbered reports. The data read 
from the device will be stored in the following bytes.

FWIW, the difficulty to find out what the code does is because this part
is handled in each HID transport driver: USB, Bluetooth, I2C.

Looking at drivers/hid/usbhid/hid-core.c, lines 869+, the function
usbhid_get_raw_report() is the one used by hidraw in the end and is
still the original code from Alan:

/* Byte 0 is the report number. Report data starts at byte 1.*/
buf[0] = report_number;
if (report_number == 0x0) {
	/* Offset the return buffer by 1, so that the report ID
	   will remain in byte 0. */
	buf++;
	count--;
	skipped_report_id = 1;
}

@mcuee
Copy link
Member Author

mcuee commented Jun 22, 2023

@Youw @tormodvolden @jonasmalacofilho @JoergAtGithub @todbot

Hopefully the above answer clear the doubt on this issue.

@Youw
Copy link
Member

Youw commented Jun 22, 2023

that is consistent with the "general statement" above
nice

@mcuee
Copy link
Member Author

mcuee commented Jun 24, 2023

BTW, I believe the following kernel code explains the behavior of Output Report handling under Linux.

For HID device without numbered report, it is okay to skip the report ID 0 for Output Report (not following HIDAPI documentation) under Linux hidraw backend. But it is better to follow HIDAPI documentation to include Report ID 0 in the buffer in order to be cross-platform code.

https://github.com/torvalds/linux/blob/master/drivers/hid/usbhid/hid-core.c

static int usbhid_output_report(struct hid_device *hid, __u8 *buf, size_t count)
{
	struct usbhid_device *usbhid = hid->driver_data;
	struct usb_device *dev = hid_to_usb_dev(hid);
	int actual_length, skipped_report_id = 0, ret;

	if (!usbhid->urbout)
		return -ENOSYS;

	if (buf[0] == 0x0) {
		/* Don't send the Report ID */
		buf++;
		count--;
		skipped_report_id = 1;
	}

	ret = usb_interrupt_msg(dev, usbhid->urbout->pipe,
				buf, count, &actual_length,
				USB_CTRL_SET_TIMEOUT);
	/* return the number of bytes transferred */
	if (ret == 0) {
		ret = actual_length;
		/* count also the report id */
		if (skipped_report_id)
			ret++;
	}

	return ret;
}

Same for I2C HID.
https://github.com/torvalds/linux/blob/master/drivers/hid/i2c-hid/i2c-hid-core.c

static int i2c_hid_get_raw_report(struct hid_device *hid,
				  u8 report_type, u8 report_id,
				  u8 *buf, size_t count)
{
	struct i2c_client *client = hid->driver_data;
	struct i2c_hid *ihid = i2c_get_clientdata(client);
	int ret_count;

	if (report_type == HID_OUTPUT_REPORT)
		return -EINVAL;

	/*
	 * In case of unnumbered reports the response from the device will
	 * not have the report ID that the upper layers expect, so we need
	 * to stash it the buffer ourselves and adjust the data size.
	 */
	if (!report_id) {
		buf[0] = 0;
		buf++;
		count--;
	}

	ret_count = i2c_hid_get_report(ihid,
			report_type == HID_FEATURE_REPORT ? 0x03 : 0x01,
			report_id, buf, count);

	if (ret_count > 0 && !report_id)
		ret_count++;

	return ret_count;
}

@mcuee
Copy link
Member Author

mcuee commented Jun 24, 2023

But I can not find similar things for bluetooth, maybe the underline Bluetooth subsystem is handling that.
https://github.com/torvalds/linux/blob/master/net/bluetooth/hidp/core.c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

3 participants