Working on spidev.h and spidev.c translation

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 ...

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.

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 ioctl(2) - Linux manual page

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.

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.

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?

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` - #14 by anon61610682

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.

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.

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` - #10 by yuyichao and Properly using `finalizer`, `ccall`, `cconvert` and `unsafe_convert` - #13 by yuyichao . I prefer not to repeat that whole thing again but let me know which part is unclear in that explaination…

1 Like

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)?

This is actually ok.

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

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

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

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

So, say the library simply adds 1 to each field passed (or to each number field of the struct passed). So, x1 and x2 would go in as 10 and 12 and come back as 11 and 13. For the whole struct - the results would be [11, 13, 15, 17].

@Frank_Applin, I think you have not read the links @yuyichao has shared with you.

There is a very useful piece of information there, which goes (verbatim from @yuyichao) :

Even though it is not called so in C, obj.field produces a lvalue reference which is why & on it gives you the address inside obj. OTOH, obj.field always produce a rvalue in julia, or in another word, local variable assignment is never significant, you can insert as many of them as you want in the code and they’ll never make any difference in semantics. This means that pointer_from_objref(m.handle) corresponds to

in your example,

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

function increment(obj::points)
  x1, y1, x2, y2 = obj.x1, obj.y1, obj.x2, obj.y2
  ccall((:incfunc, "shapes.so"), Void, (Ref{CInt}, Ref{CInt}, Ref{CInt}, Ref{CInt}), x1, y1, x2, y2)
  obj.x1 = x1
  obj.y1 = y1
  obj.x2 = x2
  obj.y2 = y2
end

is totally fine. What is not OK is to use

ccall((:incfunc, "shapes.so"), Void, (Ref{CInt}, Ref{CInt}, Ref{CInt}, Ref{CInt}), obj.x1, obj.y1, obj.x2, obj.y2)

since this is not doing what you want it to for the exact reason @yuyichao has mentioned before. When you call obj.field, you simply create a copy and change that copied variable’s value, which has no effect on the obj.field’s actual value. This is the exact same reason why you see this behaviour:

julia> mutable struct MyType
         x::Int32
         y::Float64
       end

julia> obj = MyType(1, 2.)
MyType(1, 2.0)

julia> pointer_from_objref(obj.x)
Ptr{Void} @0x00007f3c39160080

julia> pointer_from_objref(obj.y)
Ptr{Void} @0x00007f3c2d1d4f50

julia> pointer_from_objref(obj.x)
Ptr{Void} @0x00007f3c39160080

julia> pointer_from_objref(obj.y) # different address, as this time the copy of obj.y is allocated at a different place in memory
Ptr{Void} @0x00007f3c2d1d7320

julia> pointer_from_objref(obj)
Ptr{Void} @0x00007f3c2ccaac90

julia> pointer_from_objref(obj) + fieldoffset(MyType, 1)
Ptr{Void} @0x00007f3c2ccaac90

julia> pointer_from_objref(obj) + fieldoffset(MyType, 2)
Ptr{Void} @0x00007f3c2ccaac98

As you can see, the copied variables’ addresses are not correct at all!

Sorry, @yuyichao, if I am being rude to interfere, or if I am writing nonsense (which I hope I am not, after that discussion).

OK. Thanks. I’m just trying to get a grip on what retains it value and what doesn’t. Now, I see that by your example that the local variables gets changed by the ccall’s to the external function (which is suppose to increment each field by one) and you just reassign the values from the local variables back to the obj fields.

@Frank_Applin, the point in that example was to show you (since I had the very same problem in trying to pass opaque Ptr{Void} handles to a library to manage resources that are not used anymore, with the help of the finalizer feature of the garbage collector) that obj.field notation in Julia creates a copy. This is already mentioned by @yuyichao, though. I am trying to keep in mind that obj.field is equivalent to getfield(obj, :field) which is simply a function call that returns a value of type typeof(obj.field). The value is the same as obj.field, but they are not necessarily the same objects.

And in that long discussion with @yuyichao, I was also trying to understand the mechanism. By all these types of precautions that he is trying to teach us, we are either making the compiler to keep the object live until ccall finishes its job or doing what we really want to do.

Apologies to both of you if I interrupted…

Both

And

are valid code it’s just that you shouldn’t expect any of them to affect the value of obj, x1, y1, x2, y2. The Ref argument type has a special semantic that does a copy and pass by reference so only that (inaccessible) copy will be modified. If you want an object/field to be modified, you have to do that explicitly.

Note that this will give you the same value since we cache the boxed value. It still has no relation with the address of obj or the x field though.

No. This is the thing you have to do explicitly.

Keeping the object alive is another story, which is related by was what I was trying to do after making sure the right pointer is passed in the other thread.
Before worrying about GC safety, the main piece that’s missing here is to make the output parameter working. The only thing that needs to be done is to translate &obj->x1 or &x1 from C. Copying/modifying from the example in the other thread, you can do these with

x1 = Ref(obj.x1)
ccall(..., x1)
x1[]

and

ccall(..., pointer_from_objref(obj) + fieldoffset(typeof(obj), 1)))

Then see Properly using `finalizer`, `ccall`, `cconvert` and `unsafe_convert` - #10 by yuyichao for how to make the latter GC safe.

Other than special syntax from macros, whenever you have f(a) where f can be anything including ccall etc, it is impossible for it to modify the binding of a. It can only mutate a if a is mutable.