You don't have to populate every member of the f_ops structure, only those that your driver supports. If that's the case, and you have populated a few methods but left out, say, the poll method, and a user space process invokes poll(2) on your device (perhaps you've documented the fact that it's not supposed to, but what if it does?), then what will happen? In cases like this, the kernel VFS, detecting that the foo pointer (in this example, poll) is NULL, returns an appropriate negative integer (in effect, following the same 0/-E protocol). The glibc code will multiply this by -1 and set the calling process's errno variable to that value, signaling that the system call failed.
Two points to be aware of:
- Quite often, the negative errno value returned by the VFS isn't very intuitive. (For example, if you've set the read() function pointer of f_op to NULL, the VFS causes the EINVAL value to be sent back. This has the user space process think that read(2) failed because of an "Invalid argument" error, which simply isn't the case at all!)
- The lseek(2) system call has the driver seek to a prescribed location in the file – here, of course, we mean in the device. The kernel deliberately names the f_op function pointer as llseek (notice the two 'l's). This is simply to remind you that the return value from lseek can be a 64-bit (long long) quantity. Now, for the majority of hardware devices, the lseek value is not meaningful, thus most drivers do not need to implement it (unlike filesystems). Now, the issue is this: even if you do not support lseek (you've set the llseek member of f_op to NULL), it still returns a random positive value, thus causing the user-mode app to incorrectly conclude that it succeeded. Hence, if you aren't implementing lseek, you are to do the following:
- Explicitly set llseek to the special no_llseek value, which will cause a failure value (-ESPIPE; illegal seek) to be returned.
- In such cases, you are to also invoke the nonseekable_open() function in your driver's open() method, specifying that the file is non-seekable (this is often called like this in the open() method: return nonseekable_open(struct inode *inode, struct file *filp);. The details, and more, are covered in the LWN articles here: https://lwn.net/Articles/97154/. You can see the changes this wrought to many drivers here: https://lwn.net/Articles/97180/).
An appropriate value to return if you aren't supporting a function is -ENOSYS, which will have the user-mode process see the error Function not implemented (when it invokes the perror(3) or strerror(3) library APIs). This is clear, unambiguous; the user space developer will now understand that your driver does not support this function. Thus, one way to implement your driver is to set up pointers to all the file operation methods, and write a routine for all file-related system calls (the f_op methods) in your driver. For the ones you do support, write the code; for the ones you do not implement, just return the value -ENOSYS. Though a bit painstaking to do, it will result in unambiguous return values to user space.