Working on spidev.h and spidev.c translation


#1

OK. I have started working on translating spidev.h and spidev.c to Julia. I hope @yuyichao can tell me if I’m heading down the right path on this (I’m trying to somewhat follow what you advises for i2c-dev).

Here’s the C code for the main structure used:

/**
 * struct spi_ioc_transfer - describes a single SPI transfer
 * @tx_buf: Holds pointer to userspace buffer with transmit data, or null.
 *	If no data is provided, zeroes are shifted out.
 * @rx_buf: Holds pointer to userspace buffer for receive data, or null.
 * @len: Length of tx and rx buffers, in bytes.
 * @speed_hz: Temporary override of the device's bitrate.
 * @bits_per_word: Temporary override of the device's wordsize.
 * @delay_usecs: If nonzero, how long to delay after the last bit transfer
 *	before optionally deselecting the device before the next transfer.
 * @cs_change: True to deselect device before starting the next transfer.
 *
 * This structure is mapped directly to the kernel spi_transfer structure;
 * the fields have the same meanings, except of course that the pointers
 * are in a different address space (and may be of different sizes in some
 * cases, such as 32-bit i386 userspace over a 64-bit x86_64 kernel).
 * Zero-initialize the structure, including currently unused fields, to
 * accommodate potential future updates.
 *
 * SPI_IOC_MESSAGE gives userspace the equivalent of kernel spi_sync().
 * Pass it an array of related transfers, they'll execute together.
 * Each transfer may be half duplex (either direction) or full duplex.
 *
 *	struct spi_ioc_transfer mesg[4];
 *	...
 *	status = ioctl(fd, SPI_IOC_MESSAGE(4), mesg);
 *
 * So for example one transfer might send a nine bit command (right aligned
 * in a 16-bit word), the next could read a block of 8-bit data before
 * terminating that command by temporarily deselecting the chip; the next
 * could send a different nine bit command (re-selecting the chip), and the
 * last transfer might write some register values.
 */
struct spi_ioc_transfer {
	__u64		tx_buf;
	__u64		rx_buf;

	__u32		len;
	__u32		speed_hz;

	__u16		delay_usecs;
	__u8		bits_per_word;
	__u8		cs_change;
	__u8		tx_nbits;
	__u8		rx_nbits;
	__u16		pad;

	/* If the contents of 'struct spi_ioc_transfer' ever change
	 * incompatibly, then the ioctl number (currently 0) must change;
	 * ioctls with constant size fields get a bit more in the way of
	 * error checking than ones (like this) where that field varies.
	 *
	 * NOTE: struct layout is the same in 64bit and 32bit userspace.
	 */
};


I see the largest item as 64-bit - so I created the following (again trying to follow what was done with i2c-dev). I also created some helper functions to get the values out like they would have been used in the C structure.

struct spi_ioc_transfer 
	block::NTuple{4, UInt64}
end

#****************************************************************
# The following: get_tx_buf, get_rx_buf, get_len, get_speed,
# get_delay_usecs, get_bits_per_word, get_cs_change,
# get_tx_nbits, get_rx_nbits are helper functions to get the
# proper values out of the block::spi_ioc_transfer structure.
#****************************************************************
function get_tx_buf(block::spi_ioc_transfer)
	return block[1]		#1) 64-bits
end

function get_rx_buf(block::spi_ioc_transfer)
	return block[2]		#2) 64-bits
end

function get_len(block::spi_ioc_transfer)
	return block[3] & 0xFFFFFFFF00000000	#3) 32-bits
end

function set_len(len::UInt32)
	num = UInt64(len)
	return num << 32	#needs to be upper
end

function get_speed(block::spi_ioc_transfer)
	return (block[3] << 32) >> 32		#4) 32-bits
end

function get_delay_usecs(block::spi_ioc_transfer)
	return block[4] >> 48		#5) 16-bits
end

function get_bits_per_word(block::spi_ioc_transfer)
	return (block[4] << 16) >> 56		#6) 8-bits	
end

function get_cs_change(block::spi_ioc_transfer)
	return (block[4] << 24) >> 56		#7) 8-bits
end

function get_tx_nbits(block::spi_ioc_transfer)
	return (block[4] << 32) >> 56		#8) 8-bits
end

function get_rx_nbits(block::spi_ioc_transfer)
	return (block[4] << 40) >> 56		#9) 8-bits
end
#****************************************************************

and, here is how it is used in C:

int spi_xfer(int fd, uint8_t *tx_buffer, uint8_t tx_len, uint8_t *rx_buffer, uint8_t rx_len){
    struct spi_ioc_transfer spi_message[2];
    memset(spi_message, 0, sizeof(spi_message));
    
    spi_message[0].tx_buf = (unsigned long)tx_buffer;
    spi_message[0].len = tx_len;
    spi_message[1].rx_buf = (unsigned long)rx_buffer;
    spi_message[1].len = rx_len;
    
    return ioctl(fd, SPI_IOC_MESSAGE(2), spi_message);
}

And, how I’m trying to use it in Julia. I’m not sure about how to pass an array of structures (or in this case NTuples - if I’m doing this right):

function spi_xfer(fd::Int32, tx_buffer::UInt8, tx_len::UInt8, rx_buffer::UInt8, rx_len::UInt8)
	tx = spi_ioc_transfer(tx_buffer,0, set_len(tx_len),0)
	rx = spi_ioc_transfer(0,rx_buffer,set_len(rx_len),0)

	spi_message = NTuple{2, spi_ioc_transfer}((rx, tx))
    
    ret = ccall((:ioctl, "libc"), Int, (Cint, Cint, Ref{spi_ioc_transfer}), fd, SPI_IOC_MESSAGE(2), spi_message[1]) 
    return ret
end

The link to the spidev.h file has the same contents as what’s on my debian system.

http://elixir.free-electrons.com/linux/latest/source/include/uapi/linux/spi/spidev.h


#2

That advice was only for union, you have a struct here so just declare all the members.


#3

Oh, yeah - right! Thanks.


#4

ok. The structure was simple enough.

mutable struct spi_ioc_transfer 
	tx_buf::UInt64
	rx_buf::UInt64

	len::UInt32
	speed_hz::UInt32

	delay_usecs::UInt16
	bits_per_word::UInt8
	cs_change::UInt8
	tx_nbits::UInt8
	rx_nbits::UInt8
	pad::UInt16
end

Now (as always) - the ccall (and does it need …)?

function spi_xfer(fd::Int32, tx_buffer::UInt8, tx_len::UInt8, rx_buffer::UInt8, rx_len::UInt8)
	tx = spi_ioc_transfer(tx_buffer, 0, tx_len, 0, 0, 0, 0, 0, 0, 0)
	
	
	rx = spi_ioc_transfer(0, rx_buffer, rx_len, 0, 0, 0, 0, 0, 0, 0)

	spi_message = (tx,rx)
	size = Cint(length(array))

    
    ret = ccall((:ioctl, "libc"), Int, (Cint, Cint, Ptr{spi_message}, size), fd, SPI_IOC_MESSAGE(2), spi_message[1]) 
    return ret
end

#5

Your ccall looks strange. size shouldn’t be in the signature.

And I think the rule for argument mapping is pretty clear. You should be able to figure out if you need the ... just by looking at the C declaration of the function.


#6

I think this is it. So, I’ll give it a try (probably tomorrow). I dug up a plot of carrots, yesterday, and now I’m doing KP duty - cutting and cleaning them all.

ret = ccall((:ioctl, "libc"), Int, (Cint, Cint, Ptr{spi_message}), fd, SPI_IOC_MESSAGE(2), spi_message[1]) 

#7

AFAICT the ioctl declaration, which you can find in the manpage, is int ioctl(int fildes, int request, ... /* arg */); so,

  1. Return type is int not long (It’s the same on 32bit but stil…)

  2. The thrid and later arguements are vararg so yes you need the ...


#8

Yeah - this is nice:

"No single standard. Arguments, returns, and semantics of ioctl() vary according to the device driver in question (the call is used as a catch-all for operations that don’t cleanly fit the UNIX stream I/O model). "

My manpage didn’t give a type for the return and my Julia (which is 32-bit) gives Int as 32-bit:

julia> a = Int(1)
1

julia> typeof(a)
Int32

So, I assumed just type Int for the return type that it was 32-bit.


#9

This is describing how the arguments are used, not how they are passed. The way the parameters are passed, which is the only thing ccall cares about, is entirely captured in the prototype of the function.

You should report that as a package but at the least. In the mean time you can use http://man7.org/linux/man-pages/man2/ioctl.2.html

As I said, Int is the same as Cint on 32bit, but it is still the wrong one to use to not confuse the reader if anything.

And that link reminds me again that the second parameter is unsigned long on linux so you should use Clong or Culong instead of Cint.


#10

OK.

ret = ccall((:ioctl, "libc"), Cint, (Cint, Clong, Ptr{spi_message}...), fd, SPI_IOC_MESSAGE(2), spi_message[1]) 

And, thanks, once again for the help.


#11

OK. I actually have a device attached to my Raspberry Pi where I can start testing the SPI routines. It’s an MCP3008 chip and I’ve tested it with some C++ code and it works great. Now, comes the Julia issues.

The C++ code (the values are from his class):

enum SPIMODE{
		MODE0 = 0,   //!< Low at idle, capture on rising clock edge
		MODE1 = 1,   //!< Low at idle, capture on falling clock edge
		MODE2 = 2,   //!< High at idle, capture on falling clock edge
		MODE3 = 3    //!< High at idle, capture on rising clock edge
};

SPIMODE mode;
uint8_t bits;
uint32_t speed;
uint16_t delay;

int SPIDevice::setMode(SPIDevice::SPIMODE mode){
	this->mode = mode;
	if (ioctl(this->file, SPI_IOC_WR_MODE, &this->mode)==-1){
		perror("SPI: Can't set SPI mode.");
		return -1;
	}
	if (ioctl(this->file, SPI_IOC_RD_MODE, &this->mode)==-1){
		perror("SPI: Can't get SPI mode.");
		return -1;
	}
	return 0;
}

The Julia code:

type SPIDevice
    bus::UInt8
    device::UInt8
    file::Int
    filename::String
    mode::UInt8
    bits::UInt8
    speed::UInt32
    delay::UInt16
    SPIDevice(bus, device) = new(
                                bus, 
                                device, 
                                0, 
                                "/dev/spidev" * string(bus) * "." * string(device),
                                3,
                                8,
                                488000,
                                0)
end

function spi_setMode(spi::SPIDevice, mode::UInt8)
    spi.mode = mode

    print("1) spi.mode: ")
    println(spi.mode)

     # Set SPI_POL and SPI_PHA 
     ret = ccall((:ioctl, "libc"), Int, (Cint, Cuint, Ref{Cuchar}...), spi.file, SPI_IOC_WR_MODE, spi.mode) 
     if (ret < 0)
        error("SPI (SPI_IOC_WR_MODE): Can't set SPI mode.")
        return -1
     end

     print("2) spi.mode: ")
     println(spi.mode)
     
     ret = ccall((:ioctl, "libc"), Int, (Cint, Cuint, Ref{Cuchar}...), spi.file, SPI_IOC_RD_MODE, spi.mode) 
     if (ret < 0)
        error("SPI (SPI_IOC_RD_MODE): Can't get SPI mode.")
         return -1
     end

     print("3) spi.mode: ")
     println(spi.mode)

     return 0
end

I’m calling like:

include("spidev_h.jl")
include("spidev.jl")

import spidev
using spidev

function combineValues(upper::UInt8, lower::UInt8)
    return (Int16(upper) << 8) | Int16(lower)
end


println("Starting RPi SPI ADC Example")

spi = spidev.SPIDevice(0,0)
ret = spidev.spi_open(spi)
ret = spidev.spi_setMode(spi, UInt8(SPI_MODE_0))

and here’s the results:

julia> import spidev

julia> using spidev

julia> function combineValues(upper::UInt8, lower::UInt8)
           return (Int16(upper) << 8) | Int16(lower)
       end
combineValues (generic function with 1 method)

julia> println("Starting RPi SPI ADC Example")
Starting RPi SPI ADC Example

julia> spi = spidev.SPIDevice(0,0)
spidev.SPIDevice(0x00, 0x00, 0, "/dev/spidev0.0", 0x03, 0x08, 0x00077240, 0x0000)

julia> ret = spidev.spi_open(spi)
0

julia> ret = spidev.spi_setMode(spi, UInt8(SPI_MODE_0))
1) spi.mode: 0
2) spi.mode: 0
ERROR: SPI (SPI_IOC_RD_MODE): Can't get SPI mode.
Stacktrace:
 [1] spi_setMode(::spidev.SPIDevice, ::UInt8) at /home/julia-user/julia-0.6.0/bin/spidev.jl:91

So, this is telling me it got by the write call OK (SPI_IOC_WR_MODE):

   spi.mode = mode

    print("1) spi.mode: ")
    println(spi.mode)
     # Set SPI_POL and SPI_PHA 
     ret = ccall((:ioctl, "libc"), Int, (Cint, Cuint, Ref{Cuchar}...), spi.file, SPI_IOC_WR_MODE, spi.mode) 
     if (ret < 0)
        error("SPI (SPI_IOC_WR_MODE): Can't set SPI mode.")
        return -1
     end

     print("2) spi.mode: ")
     println(spi.mode)

and, get’s a -1 back on the read call (SPI_IOC_RD_MODE):

ret = ccall((:ioctl, "libc"), Int, (Cint, Cuint, Ref{Cuchar}...), spi.file, SPI_IOC_RD_MODE, spi.mode) 

Do you have any ideas?


#12

Your issue is that spi.mode is passed as reference but not as a reference to the field.

I’ve explained this in Properly using `finalizer`, `ccall`, `cconvert` and `unsafe_convert`


#13

Well, I had another issue that needed to be fixed by adding in the ioctl.h to this endeavor. I wasn’t get the correct value for SPI_IOC_RD_MODE. Now, I am. I’ll work on the ccall, now.


#14

I guess I (again) don’t understand. In the C++ code, I am simply passing a variable - yes it’s a field in a structure (not the stucture). So, to test this I changed my C++ code to use simply a variable instead of field member of a structure.

int SPIDevice::setMode(SPIDevice::SPIMODE mode){
        this->mode = mode;


        if (ioctl(this->file, SPI_IOC_WR_MODE, &mode)==-1){
                perror("SPI: Can't set SPI mode.");
                return -1;
        }
        this->mode = mode;


        if (ioctl(this->file, SPI_IOC_RD_MODE, &mode)==-1){
                perror("SPI: Can't get SPI mode.");
                return -1;
        }
       this->mode = mode;
       return 0;
}

This works just fine. So, I’m not understanding why if I do this:

function spi_setMode(spi::SPIDevice, mode::UInt8)    
       spi.mode = mode

      # Set SPI_POL and SPI_PHA      
      ret = ccall((:ioctl, "libc"), Int, (Cint, Cuint, Ref{Cuchar}...), spi.file, SPI_IOC_WR_MODE, mode)      
      if (ret < 0)        
           error("SPI (SPI_IOC_WR_MODE): Can't set SPI mode.")        
           return -1     
      end     
      spi.mode = mode

      ret = ccall((:ioctl, "libc"), Int, (Cint, Cuint, Ref{Cuchar}...), spi.file, SPI_IOC_RD_MODE, mode)      
      if (ret < 0)        
          error("SPI (SPI_IOC_RD_MODE): Can't get SPI mode.")         
          return -1     
     end     
     spi.mode = mode

     return 0
end

It should work the same way. I’m just passing a variable.


#15

Using the field isn’t the issue, using a variable has the same effect (except that now the issue is that you are not passing a reference to the variable). See Properly using `finalizer`, `ccall`, `cconvert` and `unsafe_convert` and Properly using `finalizer`, `ccall`, `cconvert` and `unsafe_convert` . I prefer not to repeat that whole thing again but let me know which part is unclear in that explaination…


#16

Holy cow - that made things way more confusing. Can we do just a couple simple examples?

Let’s say I have the following struct.

mutable struct points
    x1::Int32
    y1::Int32
    x2::Int32
    y2::Int32
end

So, how would you do a ccall to a library method that wanted 1) the whole struct and 2) just a field of the struct? Let’s say the library is “shapes.so” and the methods are point() and line(). Maybe, the “shapes.so” library adds 1 to each value. I’m sorry if this seems trivial or a waste of time, but some people (like me) learn better from examples.

So, I think the ccall should look like:

pts = points(10, 12, 14, 16)

x1 = pts.x1
y1 = pts.y1

ccall((:point, "shapes.so"), Int, (Cint, Cint), x1, x2)

ccall((:line, "shapes.so"), Int, (Ref{points}, ), pts)

So, can you explain why this simple example is wrong (because I know it is)?


#17

This is actually ok.


#18

But your library is not taking either of them. Your library is take a reference to a field of a struct.


#19

So, does that mean the first one should look like:

ccall((:point, "shapes.so"), Int, (Ref{Cint}, Ref{Cint}), x1, x2)

#20

It’s impossible to say since I don’t know and you didn’t say what they want.