Ccall() to ioctl

Hi,

I’m trying to make a ccall to ioctl, but I seem to be getting the parameter types wrong. Here’s some code:

const I2C_SLAVE   =	0x0703

addr = 0x1D

if ((file = open(filename,"r+")) < 0)
	error("Failed to open the bus.")
	# ERROR HANDLING; you can check errno to see what went wrong 
	return
end
ret = -1
ret = ccall((:ioctl, "libc"), Int, (Int, Int, Ptr{UInt8}), file, I2C_SLAVE, addr) 
if (ret < 0)
	error("Failed to acquire bus access and/or talk to slave.")
         # ERROR HANDLING; you can check errno to see what went wrong 
end

I get the following error:

ERROR: MethodError: no method matching unsafe_convert(::Type{Ptr{UInt8}}, ::UInt8)
Closest candidates are:
unsafe_convert(::Type{Ptr{UInt8}}, ::Symbol) at pointer.jl:35
unsafe_convert(::Type{Ptr{UInt8}}, ::String) at pointer.jl:37
unsafe_convert(::Type{T<:Ptr}, ::T<:Ptr) where T<:Ptr at essentials.jl:152

Stacktrace:
[1] anonymous at ./:?

Here’s the info. on iotcl():

The ioctl() function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g. terminals) may be controlled with ioctl() requests. The argument d must be an open file descriptor.

The second argument is a device-dependent request code. The third argument is an untyped pointer to memory. It’s traditionally char *argp (from the days before void * was valid C), and will be so named for this discussion.

An ioctl() request has encoded in it whether the argument is an in parameter or out parameter, and the size of the argument argp in bytes. Macros and defines used in specifying an ioctl() request are located in the file <sys/ioctl.h>.
RETURN VALUE
Usually, on success zero is returned. A few ioctl() requests use the return value as an output parameter and return a nonnegative value on success. On error, -1 is returned, and errno is set appropriately.

Thanks.

I think there is a mismatch between your interface definition of Ptr{Uint8} as third argument and providing a value of Uint8. Reread https://docs.julialang.org/en/stable/manual/calling-c-and-fortran-code/#When-to-use-T,-Ptr{T}-and-Ref{T}-1
and following.

OK. I changed it to a Ref{UInt8} and at least I’m not getting an error. So, onwards. Thanks.

So, another question is how do you get the errno from a ccall? I’m getting an -1 on return so I would like to check the errno.

RETURN VALUE

   Usually, on success zero is returned.  A few ioctl() requests use the
   return value as an output parameter and return a nonnegative value on
   success.  On error, -1 is returned, and errno is set appropriately.

ERRORS

   EBADF  fd is not a valid file descriptor.

   EFAULT argp references an inaccessible memory area.

   EINVAL request or argp is not valid.

   ENOTTY fd is not associated with a character special device.

   ENOTTY The specified request does not apply to the kind of object
          that the file descriptor fd references.

i’d assume that errno is available as symbol in the libc and that’s maybe a good reason, why this is actually an example in https://docs.julialang.org/en/stable/manual/calling-c-and-fortran-code/#Accessing-Global-Variables-1

OK. Thanks. I, also, found Base.libc.errno() and they both give the same results (22 - which is an invalid parameter).

Just fyi - this is all being done on a Raspberry Pi. I have a MMA8451 Triple-Axis accelerometer attached to the i2c pins (along with power and ground). I have a python program that is able to read it, but I wanted to see if I can do it from Julia [should just be open(), ioctl(), read(), write()]. I have a Beowulf cluster that consists of a Beaglebone Black, Raspberry Pi, Udoo X86, and a NanoPi Duo [development boards] and building a bunch routines that allow me do GPIO remotely from my Dell desktop. Now, I’m working on i2c. So, your help is greatly appreciated. I’m fairly new to Julia [just a few months], but I’m a long time programmer.

This seems to be working (at least no errors and clean return codes):

const I2C_SLAVE   =	0x0703  # Use this slave address

#//////////
#// Init I2Cdevice
#//////////
function i2c_init(filename::String, addr::UInt8)
   file = open(filename,"r+")
   fileDes = fd(file)
   if (fileDes < 0)
	error("Failed to open the bus.")
	# ERROR HANDLING; you can check errno to see what went wrong 
	return
   end
   ret = ccall((:ioctl, "libc"), Int, (Int, Int, UInt8), fileDes, I2C_SLAVE, addr) 
   if (ret < 0)
      error("Failed to acquire bus access and/or talk to slave.")
      # ERROR HANDLING; you can check errno to see what went wrong 
    end
    return fileDes
end

This is wrong, it should be (Cint, Cint, Cint...)

It is not really… That example should probably be removed since it’ll encourage wrong code. errno is a special name that’s actually a function call.

OK. That worked, too.

function i2c_init(filename::String, addr::UInt8)
   file = open(filename,"r+")
   fileDes = fd(file)
   if (fileDes < 0)
      error("Failed to open the bus.")
     # ERROR HANDLING; you can check errno to see what went wrong 
     return
   end
   ret = ccall((:ioctl, "libc"), Int, (Cint, Cint, Cint), fileDes, I2C_SLAVE, addr) 
   if (ret < 0)
      error("Failed to acquire bus access and/or talk to slave.")
      # ERROR HANDLING; you can check errno to see what went wrong 
   end
   return fileDes
end

Thanks.

Still wrong, you are missing the ...

function i2c_init(filename::String, addr::UInt8)
   file = open(filename,"r+")
   fileDes = fd(file)
   if (fileDes < 0)
      error("Failed to open the bus.")
      # ERROR HANDLING; you can check errno to see what went wrong 
      return
   end
   ret = ccall((:ioctl, "libc"), Int, (Cint, Cint, Cint...), fileDes, I2C_SLAVE, addr) 
   if (ret < 0)
      error("Failed to acquire bus access and/or talk to slave.")
       # ERROR HANDLING; you can check errno to see what went wrong 
   end
   return fileDes
end

So, what does the … signify?

https://docs.julialang.org/en/latest/manual/calling-c-and-fortran-code/#ccall/cfunction-argument-translation-guide-1

  • ... (e.g. a vararg)
    • T..., where T is the Julia type

I believe it signifies that the function uses the C varargs calling convention. Based on the signature in the man page for ioctl:

int ioctl(int fildes, unsigned long request, ...);

shouldn’t the ccall be:

ccall(:ioctl, Cint, (Cint, Culong, Cint...), fileDes, I2C_SLAVE, addr)

Note the Cint return type and the second argument being Culong rather than Cint.

Posix man page gives int ioctl(int, int, ...), Linux one gives int ioctl(int, unsigned long, ...) so I guess Culong should be used at on linux. Fortunately this doesn’t make any difference on any platforms we support. (OTOH, vararg does…)

The different ioctl() signatures could be kernel versus user space (my guess). I’m doing all of this via user space.

Both are userspace. The Linux header gives unsigned long so that should be used. Since as I said, the interger type actually doesn’t matter that much for any architectures, the linux signature is basically an extension to the posix signature.

P.S. On the kernel side, everything is passed as long in completely different way.