rustusbioctlusb-hid

How to use ioctl + nix macros to get a variable size buffer


This is related to How to use nix's ioctl? but it is not the same question.

I want to retrieve a variable size buffer. There is another ioctl that tells me that I need to read X bytes. The C header tells me the following too:

#define HID_MAX_DESCRIPTOR_SIZE     4096
#define HIDIOCGRDESC        _IOR('H', 0x02, struct hidraw_report_descriptor)

struct hidraw_report_descriptor {
    __u32 size;
    __u8 value[HID_MAX_DESCRIPTOR_SIZE];
};

I define the macro in the following way:

ioctl_read_buf!(hid_read_descr, b'H', 0x02, u8);

And later call:

let mut desc_raw = [0u8; 4 + 4096];
let err = unsafe { hid_read_descr(file.as_raw_fd(), &mut desc_raw); };

When doing this, desc_raw is full of zeros. I would have expected the first 4 bytes to contain size based on the struct definition.

The alternative, does not seem to work either

ioctl_read!(hid_read_descr2, b'H', 0x02, [u8; 4+4096]);
// ...
let mut desc_raw = [0xFFu8; 4 + 4096];
let err = unsafe { hid_read_descr2(file.as_raw_fd(), &mut desc_raw); };

In both cases, I have tried initializing desc_raw with 0xFF and after the call, it seems untouched.

Am I using the ioctl_read_buf macro incorrectly?


Solution

  • Now that Digikata has thoughtfully provided enough code to drive the program...

    Am I using the ioctl_read_buf macro incorrectly?

    I'd say that using it at all is incorrect here. You don't want to read an array of data, you want to read a single instance of a specific type. That's what ioctl_read! is for.

    We define a repr(C) struct that mimics the C definition. This ensures that important details like alignment, padding, field ordering, etc., all match one-to-one with the code we are calling.

    We can then construct an uninitialized instance of this struct and pass it to the newly-defined function.

    use libc; // 0.2.66
    use nix::ioctl_read; // 0.16.1
    use std::{
        fs::OpenOptions,
        mem::MaybeUninit,
        os::unix::{fs::OpenOptionsExt, io::AsRawFd},
    };
    
    const HID_MAX_DESCRIPTOR_SIZE: usize = 4096;
    
    #[repr(C)]
    pub struct hidraw_report_descriptor {
        size: u32,
        value: [u8; HID_MAX_DESCRIPTOR_SIZE],
    }
    
    ioctl_read!(hid_read_sz, b'H', 0x01, libc::c_int);
    ioctl_read!(hid_read_descr, b'H', 0x02, hidraw_report_descriptor);
    
    fn main() -> Result<(), Box<dyn std::error::Error>> {
        let file = OpenOptions::new()
            .read(true)
            .write(true)
            .custom_flags(libc::O_NONBLOCK)
            .open("/dev/hidraw0")?;
    
        unsafe {
            let fd = file.as_raw_fd();
    
            let mut size = 0;
            hid_read_sz(fd, &mut size)?;
            println!("{}", size);
    
            let mut desc_raw = MaybeUninit::<hidraw_report_descriptor>::uninit();
            (*desc_raw.as_mut_ptr()).size = size as u32;
            hid_read_descr(file.as_raw_fd(), desc_raw.as_mut_ptr())?;
            let desc_raw = desc_raw.assume_init();
            let data = &desc_raw.value[..desc_raw.size as usize];
            println!("{:02x?}", data);
        }
    
        Ok(())
    }