I'm currently developing a Linux device driver and am currently putting the whole character device infrastructure business in place; mostly boring stuff, populate file_operations
structure with handler functions and in parallel I'm writing a small test suite in Python.
Relevant code kernel side (not much to see here really)
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 32)
/* We try to keep the preprocessor #if/#endif mayhem to a minimum, but
* this is one of the few places where there's no way around it, other
* than obfuscating the function definitions behind proprocessor mayhem
* at a different place.
*
* The following two helper macros abstract away the kernel version specific
* ioctl function prototypes and access to the file pointer inode. */
#define IOCTL_FUNC(name, _inode, _filp, _cmd, _args) \
int name(struct inode *_inode, struct file *_filp, unsigned int _cmd, unsigned long _args)
#define IOCTL_INODE(_filp, _inode) \
(void)_inode;
#else
#define IOCTL_FUNC(name, _inode, _filp, _cmd, _args) \
long name(struct file *_filp, unsigned int _cmd, unsigned long _args)
# define IOCTL_INODE(_filp, _inode) \
struct inode *_inode = file_inode(_filp); \
(void)_inode;
#endif
static
int dwdsys_dev_from_inode_or_file(
struct inode *inode,
struct file *filp,
struct dwddev **out_dwd )
{
int rc = -ENODEV;
struct dwddev *dwd = NULL;
struct dwdsys_linux *dsl;
list_for_each_entry( dsl, &dwdsys_list, entry ){
if( MAJOR(asl->devno_base) == MAJOR(inode->i_rdev) ){
unsigned const i_board = MINOR(inode->i_rdev);
if( i_board < dsl->ds.n_boards ){
dwd = dsl->ds.board[i_board];
rc= 0;
break;
}
}
}
/* XXX: cache dwd in either filp->private_data or inode->i_private */
if( !rc ){
if( out_dwd ){ *out_dwd = dwd; }
}
return rc;
}
static IOCTL_FUNC(dwdsys_chrdev_ioctl, inode, filp, cmd, args)
{
int rc;
struct dwddev *dwd= NULL;
IOCTL_INODE(filp, inode);
rc= dwdsys_dev_from_inode_or_file(inode, filp, &dwd);
if( rc ){ goto fail; }
switch( cmd ){
default:
rc= -EINVAL;
break;
case ...:
/* ... */
}
fail:
DWD__TRACE_FUNCTION(dwd, rc, "cmd=0x%08x, args=%p", cmd, (void*)args);
return rc;
}
static struct file_operations dwdsys_chrdev_fops = {
.owner = THIS_MODULE,
#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 30)
.ioctl = dwdsys_chrdev_ioctl,
#else
.unlocked_ioctl = dwdsys_chrdev_ioctl,
#endif
};
And I just noticed that when I open the driver's device node within the Python interactive REPL the driver will report an unsupported ioctl call, command code 0x5413 which translates into TIOCSWINSZ
. That would be ioctl that's being used on VTs/PTYs to set the window size. I can see why the Python REPL would do that ioctl on, say, stdio. But it seems an odd thing to do unconditionally.
Here's what I do in the Python REPL
>>> dwd = open("/dev/dwd0a", "r")
That's it. This will make my driver spit a warning into the kernel log, that an unsupported ioctl was called.
So the question is: Is this intended, specified behaviour? Or is this something unintended, they maybe should be reported as a bug?
dw@void: ~/dwd/src/linux master ⚡
$ python
Python 3.5.2 (default, Oct 19 2016, 17:19:49)
[GCC 4.9.4] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> dwd = open("/dev/dwd0a", "r")
[1] + 1906 suspended python
At this point, before hitting enter I suspended the Python REPL process, and attached strace to it, before foregrounding it again…
dw@void: ~/dwd/src/linux master ⚡
$ sudo strace -p 1906 &
[2] 1922
strace: Process 1906 attached
--- stopped by SIGTSTP ---
dw@void: ~/dwd/src/linux master ⚡
$ fg
[1] - 1906 continued python
--- SIGCONT {si_signo=SIGCONT, si_code=SI_USER, si_pid=738, si_uid=1000} ---
select(1, [0], NULL, NULL, NULL) = 1 (in [0])
rt_sigaction(SIGWINCH, {0x7fd1524463e0, [], SA_RESTORER|SA_RESTART, 0x7fd152fbcbef}, {0x7fd152668980, [], SA_RESTORER, 0x7fd152fbcbef}, 8) = 0
read(0, "\n", 1) = 1
writev(1, [{iov_base="", iov_len=0}, {iov_base="\n", iov_len=1}], 2) = 1
ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = 0
rt_sigaction(SIGWINCH, {0x7fd152668980, [], SA_RESTORER, 0x7fd152fbcbef}, {0x7fd1524463e0, [], SA_RESTORER|SA_RESTART, 0x7fd152fbcbef}, 8) = 0
open("/dev/dwd0a", O_RDONLY|O_CLOEXEC) = 3
fcntl(3, F_SETFD, FD_CLOEXEC) = 0
fstat(3, {st_mode=S_IFCHR|0666, st_rdev=makedev(240, 0), ...}) = 0
ioctl(3, TIOCGWINSZ, 0x7fffb1291f00) = -1 EINVAL (Invalid argument)
lseek(3, 0, SEEK_CUR) = -1 ESPIPE (Invalid seek)
ioctl(3, TIOCGWINSZ, 0x7fffb1291eb0) = -1 EINVAL (Invalid argument)
getcwd("/home/dw/dwd/src/linux", 1024) = 31
stat("/home/dw/dwd/src/linux", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
stat("/usr/lib/python3.5", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
stat("/usr/lib/python3.5/_bootlocale.py", {st_mode=S_IFREG|0644, st_size=1301, ...}) = 0
stat("/usr/lib/python3.5/_bootlocale.py", {st_mode=S_IFREG|0644, st_size=1301, ...}) = 0
open("/usr/lib/python3.5/__pycache__/_bootlocale.cpython-35.pyc", O_RDONLY|O_CLOEXEC) = 4
fcntl(4, F_SETFD, FD_CLOEXEC) = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=1028, ...}) = 0
lseek(4, 0, SEEK_CUR) = 0
fstat(4, {st_mode=S_IFREG|0644, st_size=1028, ...}) = 0
read(4, "\26\r\r\n5\253\7X\25\5\0\0\343\0\0\0\0\0\0\0\0\0\0\0\0\v\0\0\0@\0\0"..., 1029) = 1028
read(4, "", 1) = 0
close(4) = 0
lseek(3, 0, SEEK_CUR) = -1 ESPIPE (Invalid seek)
brk(0x562c9d5c0000) = 0x562c9d5c0000
ioctl(0, TIOCGWINSZ, {ws_row=45, ws_col=115, ws_xpixel=809, ws_ypixel=589}) = 0
ioctl(1, TIOCGWINSZ, {ws_row=45, ws_col=115, ws_xpixel=809, ws_ypixel=589}) = 0
ioctl(0, TIOCGWINSZ, {ws_row=45, ws_col=115, ws_xpixel=809, ws_ypixel=589}) = 0
ioctl(0, TIOCSWINSZ, {ws_row=45, ws_col=115, ws_xpixel=809, ws_ypixel=589}) = 0
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
ioctl(0, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig -icanon -echo ...}) = 0
writev(1, [{iov_base=">>> ", iov_len=4}, {iov_base=NULL, iov_len=0}], 2>>> ) = 4
I've some similar problems when calling an ioctl of a (custom) linux driver. I found out that using os.open solves my problem. In the description of os.open they say that it is intended for low-level I/O. So maybe you shouldn't open device nodes with build-in open() even if everybody does it? If it wouldn't be a custom driver with an error message about unknown ioctls I would have never recognised that there are some other ioctls anyway.
Using built-in open():
with open('/dev/hdmi_0_0_0', 'r') as fd:
fcntl.ioctl(fd, 0x40084814, reg_acc)
=>
openat(AT_FDCWD, "/dev/hdmi_0_0_0", O_RDONLY|O_LARGEFILE|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFCHR|0600, st_rdev=makedev(239, 0), ...}) = 0
ioctl(3, TCGETS, 0xffe55e68) = -1 ENOSYS (Function not implemented)
_llseek(3, 0, 0xffe55d58, SEEK_CUR) = -1 ESPIPE (Illegal seek)
ioctl(3, TCGETS, 0xffe55e08) = -1 ENOSYS (Function not implemented)
_llseek(3, 0, 0xffe55c48, SEEK_CUR) = -1 ESPIPE (Illegal seek)
ioctl(3, _IOC(_IOC_READ, 0x48, 0x14, 0x8), 0xffe55c38) = 0
Using os.open():
fd = os.open('/dev/hdmi_0_0_0', os.O_RDWR | os.O_SYNC)
fcntl.ioctl(fd, 0x40084814, reg_acc)
os.close(fd)
=>
openat(AT_FDCWD, "/dev/hdmi_0_0_0", O_RDWR|O_SYNC|O_LARGEFILE|O_CLOEXEC) = 3
ioctl(3, _IOC(_IOC_READ, 0x48, 0x14, 0x8), 0xffa076e8) = 0