Ruby,ioctl和复杂的结构

我有一个硬件,我试图通过我的计算机的内置SPI驱动程序来控制. SPI驱动程序通过ioctl控制.

我可以从小型C程序中成功驱动硬件;但是当我尝试在Ruby中复制C程序时遇到了问题.

使用IO#ioctl设置基本寄存器(使用u32和u8整数)工作正常(我知道因为我也可以使用ioctl读回我设置的值);但是一旦我尝试设置一个复杂的结构,程序就失败了

small.rb:51:in 'ioctl': Connection timed out @ rb_ioctl - /dev/spidev32766.0 (Errno::ETIMEDOUT)

我可能遇到麻烦,因为spi_ioc_transfer struct有两个指向字节缓冲区的指针,但是即使在32位平台上也指向无符号64位整数指针 – 需要在C中转换为(无符号长整数).我正在尝试在Ruby中复制它,但我对自己很不确定.

下面是工作的C程序和不起作用的Ruby端口. do_latch函数是必需的,所以我可以在我的硬件中看到结果;但可能与这个问题没有密切关系.

C(有效):

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>

int do_latch() {
    int fd = open("/sys/class/gpio/gpio1014/value", O_RDWR);
    write(fd, "1", 1);
    write(fd, "0", 1);

    close(fd);
}

int do_transfer(int fd, uint8_t *bytes, size_t len) {
    uint8_t *rx_bytes = malloc(sizeof(uint8_t) * len);

    struct spi_ioc_transfer transfer = {
        .tx_buf = (unsigned long)bytes,
        .rx_buf = (unsigned long)rx_bytes,

        .len = len,
        .speed_hz = 100000,

        .delay_usecs = 0,
        .bits_per_word = 8,
        .cs_change = 0,
        .tx_nbits = 0,
        .rx_nbits = 0,
        .pad = 0
    };

    if(ioctl(fd, SPI_IOC_MESSAGE(1), &transfer) < 1) {
        perror("Could not send SPI message"); 
        exit(1); 
    }

    free(rx_bytes);
}

int main() {
    int fd = open("/dev/spidev32766.0", O_RDWR);

    uint8_t mode = 0;
    ioctl(fd, SPI_IOC_WR_MODE, &mode);

    uint8_t lsb_first = 0;
    ioctl(fd, SPI_IOC_WR_LSB_FIRST, lsb_first);

    uint32_t speed_hz = 100000;
    ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, speed_hz);

    size_t data_len = 36;
    uint8_t *tx_data = malloc(sizeof(uint8_t) * data_len);

    memset(tx_data, 0xFF, data_len);

    do_transfer(fd, tx_data, data_len);
    do_latch();

    sleep(2);

    memset(tx_data, 0x00, data_len);

    do_transfer(fd, tx_data, data_len);
    do_latch();

    free(tx_data);
    close(fd);
    return 0;
}

Ruby(在do_transfer的ioctl行上失败):

SPI_IOC_WR_MODE = 0x40016b01
SPI_IOC_WR_LSB_FIRST = 0x40016b02
SPI_IOC_WR_BITS_PER_WORD = 0x40016b03
SPI_IOC_WR_MAX_SPEED_HZ = 0x40046b04
SPI_IOC_WR_MODE32 = 0x40046b05
SPI_IOC_MESSAGE_1 = 0x40206b00 

def do_latch()
  File.open("/sys/class/gpio/gpio1014/value", File::RDWR) do |file|
    file.write("1")
    file.write("0")
  end
end

def do_transfer(file, bytes)
  ##########################################################################################
  #begin spi_ioc_transfer struct (cat /usr/include/linux/spi/spidev.h)

    #pack bytes into a buffer; create a new buffer (filled with zeroes) for the rx
    tx_buff = bytes.pack("C*")        
    rx_buff = (Array.new(bytes.size) { 0 }).pack("C*")       

    #on 32-bit, the struct uses a zero-extended pointer for the buffers (so it's the same
    #byte layout on 64-bit as well) -- so do some trickery to get the buffer addresses 
    #as 64-bit strings even though this is running on a 32-bit computer
    tx_buff_pointer = [tx_buff].pack("P").unpack("L!")[0]   #u64 (zero-extended pointer)
    rx_buff_pointer = [rx_buff].pack("P").unpack("L!")[0]   #u64 (zero-extended pointer)

    buff_len = bytes.size                                   #u32
    speed_hz = 100000                                       #u32

    delay_usecs = 0                                         #u16
    bits_per_word = 8                                       #u8
    cs_change = 0                                           #u8
    tx_nbits = 0                                            #u8
    rx_nbits = 0                                            #u8
    pad = 0                                                 #u16

    struct_array = [tx_buff_pointer, rx_buff_pointer, buff_len, speed_hz, delay_usecs, bits_per_word, cs_change, tx_nbits, rx_nbits, pad]
    struct_packed = struct_array.pack("QQLLSCCCCS")

    #in C, I pass a pointer to the the structure; so mimic that here
    struct_pointer_packed = [struct_packed].pack("P")
  #end spi_ioc_transfer struct
  ##########################################################################################

  file.ioctl(SPI_IOC_MESSAGE_1, struct_pointer_packed)
end

File.open("/dev/spidev32766.0", File::RDWR) do |file|

  file.ioctl(SPI_IOC_WR_MODE, [0].pack("C"));
  file.ioctl(SPI_IOC_WR_LSB_FIRST, [0].pack("C"));
  file.ioctl(SPI_IOC_WR_MAX_SPEED_HZ, [0].pack("L"));

  data_bytes = Array.new(36) { 0x00 }

  do_transfer(file, data_bytes)
  do_latch()

  sleep(2) 

  data_bytes = []
  data_bytes = Array.new(36) { 0xFF }

  do_transfer(file, data_bytes)
  do_latch()

end

我通过C打印它们来消除幻数常量(它们是C中的宏).我可以证实他们中的大多数都有效;我对ioctl消息失败(SPI_IOC_MESSAGE_1)有点不确定,因为它不起作用,而且它是一个复杂的宏.尽管如此,我没有理由认为这是不正确的,当我从C看它时它总是一样的.

当我在C中打印出结构然后在Ruby中打印出来时,唯一的区别在于缓冲区地址,所以如果出现问题,感觉就像是正确的地方.但是我已经没有什么可试的了.

我也可以打印出两个版本的地址,它们看起来像我期望的那样,32位扩展到64位,并匹配结构中的值(虽然结构是小端 – 这是一个ARM).

C中的结构(有效):

60200200 00000000 a8200200 00000000 24000000 40420f00 00000800 00000000

Ruby中的结构(失败):

a85da27f 00000000 08399b7f 00000000 24000000 40420f00 00000800 00000000

当我在Ruby中布局结构时,是否有一个明显的错误?还有别的东西让我失踪吗?

我的下一步是用C编写一个库,并使用FFI从Ruby访问它.但这似乎放弃了;如果我能使它工作,使用本机ioctl功能感觉就像是更好的方法.

更新

上面,我正在做

    struct_array = [tx_buff_pointer, rx_buff_pointer, buff_len, speed_hz, delay_usecs, bits_per_word, cs_change, tx_nbits, rx_nbits, pad]
    struct_packed = struct_array.pack("QQLLSCCCCS")

    #in C, I pass a pointer to the the structure; so mimic that here
    struct_pointer_packed = [struct_packed].pack("P")
    file.ioctl(SPI_IOC_MESSAGE_1, struct_pointer_packed)

因为我必须将指针传递给C中的结构.但这就是造成错误的原因!

相反,它需要

    struct_array = [tx_buff_pointer, rx_buff_pointer, buff_len, speed_hz, delay_usecs, bits_per_word, cs_change, tx_nbits, rx_nbits, pad]
    struct_packed = struct_array.pack("QQLLSCCCCS")

    file.ioctl(SPI_IOC_MESSAGE_1, struct_packed)

我猜Ruby会在它编组时自动将它变成一个数组吗?

不幸的是,现在它只能间歇性地工作.第二个调用永远不会起作用,如果我传入全零,第一个调用就不起作用.这很神秘.

最佳答案 不刷新缓冲区是一个常见的问题,你可以检查一下并试一试.

Flush
将ios中的任何缓冲数据刷新到底层操作系统(请注意,这只是Ruby内部缓冲;操作系统也可以缓冲数据).

rb_io_flush(VALUE io)
{
    return rb_io_flush_raw(io, 1);
}
点赞