I am implementing a device driver in userspace in python with the help of ctypes
and the CUSE
part of FUSE
. This is not my first CUSE
driver, and in the past I have been able to do the following to return a structure.
class tvStruct(Structure):
_fields_ = [("tv_sec",c_long),
("tv_usec",c_long)]
The timeval structure can be passed back to an application that uses an IOCTL call with the appropriate number. The following is the IOCTL file operation for the CUSE
driver.
def ioctl(self, req, cmd, arg_p, file_info, uflags, in_buff_p, in_bufsz, out_bufsz):
ioctl = DECODE_IOC(cmd)
if ioctl == IOC_READ_TIMEVAL:
if not in_buff_p:
PyCuse.fuse_reply_ioctl_retry(req,
pointer(PyCuse.iovec(cast(arg_p,c_void_p),
sizeof(tvStruct))),1,
pointer(PyCuse.iovec(cast(arg_p,c_void_p),
sizeof(tvStruct))),1)
else:
tvPtr = cast(in_buff_p,POINTER(tvStruct))
PyCuse.fuse_reply_ioctl(req, 0, tvPtr, sizeof(tvStruct))
else:
print("%s Unrecognized IOCTL #...\n",self.devname)
PyCuse.fuse_reply_err(req,1)
The current driver I am developing needs to return a structure containing a pointer to a character array.
# C++ Struct
typedef struct {
__u16 addr;
__u16 length;
__u8* pBuf;
} EEPromData;
# Python Class
class EEPromDataStruct(Structure):
_fields_ = [("addr",c_ushort),
("length",c_ushort),
("pBuf",POINTER(c_char))] # I have tried several other ctypes types
# for `pBuf`, but to no avail yet
I understand that I can't assign a pointer from Python to pBuf
because it is not the same memory context. I also understand that this is somewhat easy from kernel space with the use of copy_to_user
. I am bound by requirements that this be a python application because it is attached to a python GUI. I appreciate any and all help.
I know that this can be solved via the fuse_reply_ioctl_retry
method. This method allows me to copy data from and to the application. The problem now is that upon a second call to fuse_reply_ioctl_retry
, FUSE
returns an error code to the application. I will continue trying to fix this, but would appreciate any help in the meantime. Thanks.
I have figured it out. The trick is to call fuse_reply_ioctl_retry
for every pointer in the structure that you want to access the contents of, whether it be to modify them or just to read from them. This proved to be quite the hassle using python
and ctypes
along with FUSE/CUSE
and if anyone else is doing this, I would strongly recommend switching to straight C for this application because ctypes
can be tricky enough, but on top of CUSE/FUSE
systems, it is pretty horrendous. I would have, but I was bound to python because this was an add-on to another application. Mini-rant over.
Here is a stripped down version of the solution.
# ctypes Structure for driver
class EEPromDataStruct(Structure):
_fields_ = [("addr",c_ushort),
("length",c_ushort),
("pBuf",c_ulong)] #u8*
# Note that this is a long instead of some pointer.
# This is so I can easily know the application address
# the pointer holds.
...
# The IOCTL file operation for this driver
def ioctl(self, req, cmd, arg_p, file_info, uflags, in_buff_p,
in_bufsz, out_bufsz):
ioctl = DECODE_IOC(cmd)
if ioctl == IOC_READ_EEPROM:
if not in_buff_p:
PyCuse.fuse_reply_ioctl_retry(req,pointer(PyCuse.iovec(
cast(arg_p,c_void_p),sizeof(
EEPromDataStruct))),1,None,0)
else:
if out_bufsz == 0:
eepromDataPtr = cast(in_buff_p,POINTER(EEPromDataStruct))
addr = eepromDataPtr.contents.addr
length = eepromDataPtr.contents.length
pBuf = eepromDataPtr.contents.pBuf
# Load from pBuf
out_iovecs = pointer(PyCuse.iovec(cast(pBuf,c_void_p),length))
in_iovecs = pointer(PyCuse.iovec(cast(pBuf,c_void_p),length))
PyCuse.fuse_reply_ioctl_retry(req,in_iovecs,1,out_iovecs,1)
else:
eepromBuff.value = "Some String Here"
PyCuse.fuse_reply_ioctl(req, 0, eepromBuff, out_bufsz)
else:
print("%s Unrecognized IOCTL #...\n",self.devname)
PyCuse.fuse_reply_err(req,1)
if not in_buff_p
will check if application memory has been loaded into the driver, and if not, it will call fuse_reply_ioctl_retry
to do so. Note that one main difference now is that the output data vector specified for this retry was None
. This is important because I then check to see what the output size is. If it is 0, then I know that it is my main EEPromDataStruct
and I continue with that path. I prepare some data vectors and retry again. Finally, I take the pointer and assign some string to its contents.
Its a lengthy process. But it works. I hope this will help anyone who needs it.