nvcc under linux complains: Contains a vector, which is not supported in device code - linux

I have the following code
#include <cuda.h>
#include <cuda_runtime.h>
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
//A bitset for the variable assignments
//The state for non existing variable 0 is stored as well, just to avoid +1/-1 adjustments
struct Atom_t {
enum where { device, host};
enum BoolOp {opXor, opOr, opAnd };
public: //TODO make private later
int VarCount;
bool isValid;
union {
uint32_t raw[1]; //don't worry about alignment, the compiler will not use aligned read/writes anyway.}
uint64_t raw64[1];
__m256i avxraw[1];
};
public:
__host__ __device__ friend bool operator==(const Atom_t& a, const Atom_t& b);
};
__host__ __device__ bool operator==(const Atom_t& a, const Atom_t& b) {
const auto IntCount = a.IntCount();
if (IntCount != b.IntCount()) { return false; }
#ifdef __CUDA_ARCH__
__shared__ bool isDifferent;
isDifferent = false;
for (auto i = ThreadId(); i < IntCount; i += BlockDim()) {
if (a.raw[i] != b.raw[i] || isDifferent) {
isDifferent = true;
break;
}
}
syncthreads();
return !isDifferent;
#else
auto result = true;
#ifdef _DEBUG
for (auto i = 0; i < IntCount; i++) {
if (a.raw[i] != b.raw[i]) { result = false; }
}
#endif
auto AvxCount = a.Avx2Count();
if (AvxCount != b.Avx2Count()) { if (result) { print("Atom_t == is incorrect"); } assert1(!result); return false; }
for (auto i = 0; i < AvxCount; i++) {
const auto packedCompare = _mm256_cmpeq_epi8(a.avxraw[i], b.avxraw[i]);
const auto bitmask = _mm256_movemask_epi8(packedCompare);
if (bitmask != -1) { if (result) { print("Atom_t == is incorrect"); } assert1(!result); return false; }
}
#endif
#ifndef __CUDA_ARCH__
assert(result);
#endif
return true;
}
The compiler complains
Description Resource Path Location Type
"__nv_bool (const Atom_t &, const Atom_t &)" contains a vector, which is not supported in device code
However, the vector is not in device code, only in the host code. How do I make this error go away in NSight Eclipse Edition 9.1 running CUDA 11.
I tried:
#ifdef __CUDA_ARCH__
# define DEAL_II_COMPILER_VECTORIZATION_LEVEL 0
#endif
But that does not work.

However, the vector is not in device code, only in the host code.
The error is coming about due to this line:
__m256i avxraw[1];
which is visible in both the host code and device code compilation trajectory.
According to my testing this may be a possible workaround:
$ cat t32.cpp
#ifdef _MSC_VER
#include <intrin.h>
#else
#include <x86intrin.h>
#endif
#include <iostream>
typedef char dummy[sizeof(__m256i)];
struct Atom_t {
enum where { device, host};
enum BoolOp {opXor, opOr, opAnd };
public: //TODO make private later
int VarCount;
bool isValid;
union {
uint32_t raw[1];
uint64_t raw64[1];
#ifndef FOO //hide the vectorized datastruct from cuda's view
__m256i avxraw[1];
#else
alignas(32) dummy foo[1];
#endif
};
};
int main(){
std::cout << sizeof(__m256i) << std::endl;
std::cout << sizeof(Atom_t) << std::endl;
}
$ g++ t32.cpp -o t32
$ ./t32
32
64
$ g++ t32.cpp -o t32 -DFOO
$ ./t32
32
64
(Fedora Core 29)
The alignas(32) directive is still probably somewhat fragile if the definition of __m256i changes dramatically. And, clearly, the above is not CUDA code in the exact frame that was presented. It would need to be adapted (e.g. replace #ifndef FOO with #ifndef __CUDA_ARCH__)
I'm not suggesting that this code is correct, defect-free, or suitable for any particular purpose; it is mostly code provided by OP. My objective here is to identify issues that I see and are asked about in the question, and suggest possible ways to address those issues. Use this at your own risk.

Found it!
The problem is not the code in the method, the problem is the presence of the _m256i within view of cuda.
The following patch fixes the issue:
struct Atom_t {
enum where { device, host};
enum BoolOp {opXor, opOr, opAnd };
public: //TODO make private later
int VarCount;
bool isValid;
union {
uint32_t raw[1]; //don't worry about alignment, the compiler will not use aligned read/writes anyway.}
uint64_t raw64[1];
#ifndef __CUDA_ARCH__ //hide the vectorized datastruct from cuda's view
__m256i avxraw[1];
#endif
};
Now that nvcc does not see the vectorized datatype it will stop worrying.

Related

Get function from x64 instruction pointers?

This is an exercise that I want to implement in real code
I send a signal to my app (x86-64 linux). My app then executes code that walks the stack and prints out instruction pointers. I'm not sure if I want only the last few or everything to main. Anyway, I'm releasing an optimized binary without debug information. I strip symbols before its distributed.
I was wondering, how do I translate it back? I don't need to translate it in the app. I can use the machine I build to go from rip's to functions. I was thinking maybe I should also distribute one with debug information and maybe have the user be able to see the function+line but I think line will be unlikely if its optimized well
Another problem I have is my code doesn't seem to walk past the signal function. backtrace figures it out but I'm trying to do this without libc. Here's some code
#include <signal.h>
#include <cstdio>
typedef unsigned long long u64;
int mybacktrace();
#include <execinfo.h>
#include <unistd.h>
void print_stacktrace(void) {
size_t size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
backtrace_symbols_fd(array, size, STDOUT_FILENO);
}
void mysig(int signo) {
mybacktrace();
_exit(1);
}
int mybacktrace() {
u64*p;
p = (u64*)((u64)&p + 16); //seems to work correctly
for (int i = 0; i < 10 && (u64)p >= 1<<16; i++)
{
printf("%d %p\n", i, p[1]);
p = (u64*)(p[0]);
}
print_stacktrace(); return 0;
return 0;
}
int test()
{
return mybacktrace();
}
int main(int argc, char *argv[])
{
signal(SIGILL, mysig);
test();
__builtin_trap();
return 0;
}

"too many memory references for mov" error when embedding assembly in C++

I wrote a program to check if I am working with virtual machine or not using assembly for linux. When I compile it, it shows the error "too many memory references for mov". How can I fix this? Can you guys help me check if my program is correct or not?
#include <stdio.h>
#include <iostream>
using namespace std;
bool IsInsideVMWare() {
bool rc = true;
try {
asm (
"push   edx;"
"push   ecx;"
"push   ebx;"
"mov    eax, 'VMXh';"
"mov    ebx, 0;" // any value but not the MAGIC VALUE
"mov    ecx, 10;" // get VMWare version
"mov    edx, 'VX';" // port number
"in     eax, dx;" // read port on return EAX returns the VERSION
"cmp    ebx, 'VMXh';" // is it a reply from VMWare?
"setz   [rc];" // set return value
"pop    ebx;"
"pop    ecx;"
"pop    edx;"
);
}
catch(const bool a) {
rc = false;
}
return rc;
}
int main() {
if (IsInsideVMWare()) {
cout << "VM Ware";
}
return 0;
}
You seem to have copied code intended for MSVC. I have converted to gcc inline assembly for you:
#include <stdint.h>
#include <iostream>
using namespace std;
bool IsInsideVMWare() {
bool rc = true;
try {
const uint32_t magic = 0x564D5868; // VMXh
const uint16_t port = 0x5658; // VX
uint32_t result, flag;
asm ("in %[port], %[result]"
: [result] "=a" (result), "=b" (flag)
: "0" (magic), "c" (10), [port] "d" (port));
rc = (flag == magic);
}
catch(const bool a) {
rc = false;
}
return rc;
}
int main() {
if (IsInsideVMWare()) {
cout << "VM Ware";
}
return 0;
}
Note that when not running under VMWare the in instruction requires privileges otherwise you get a segfault that the try/catch will not handle. As a workaround you can run as root and enable the privileges while hoping that accessing the vmware port will be harmless on your system:
#include <stdint.h>
#include <sys/io.h>
#include <iostream>
using namespace std;
bool IsInsideVMWare() {
const uint32_t magic = 0x564D5868; // VMXh
const uint16_t port = 0x5658; // VX
uint32_t result, flag;
iopl(3);
asm ("in %[port], %[result]"
: [result] "=a" (result), "=b" (flag)
: "0" (magic), "c" (10), [port] "d" (port));
return (flag == magic);
}
int main() {
if (IsInsideVMWare()) {
cout << "VM Ware";
}
return 0;
}
Depending on your requirements, it may be better to check the DMI product or vendor name, both of which contain "VMWare" on my system.

Can't open file when calling golang function via Node

I followed the tutorial in https://medium.com/learning-the-go-programming-language/calling-go-functions-from-other-languages-4c7d8bcc69bf to make my node app able to call golang function. The provided example works like a charm.
I do, however, unable it to implement in another scenario. Here I want to open a file by providing only it's absolute path and call the Go's function, but it always told me that it can't find the file due to no such file. I'm trying to run it directly in Go and it works!
Am I doing it wrong or is it an actual bug/unfinished feature?
Here is the golang source that I've built to c-style lib :
package main
import "C"
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {}
//export ReadSomething
func ReadSomething(filePath string) {
file, err := os.Open(filePath)
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
}
I built it with this command :
go build -buildmode=c-shared -o simpleread.so main.go
In case you're wondering what's the header output :
/* Created by "go tool cgo" - DO NOT EDIT. */
/* package command-line-arguments */
#line 1 "cgo-builtin-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
typedef _GoString_ GoString;
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern void ReadSomething(GoString p0);
#ifdef __cplusplus
}
#endif
And then, below is how I call it via Node. I give the comment on the line where the error generated :
var ref = require("ref")
var ffi = require("ffi-napi")
var Struct = require("ref-struct")
var ArrayType = require("ref-array")
var LongArray = ArrayType(ref.types.longlong);
var GoString = Struct({
p: "string",
n: "longlong"
});
var simpleRead = ffi.Library("./simpleread.so", {
ReadSomething: ["void", [GoString]]
});
// error here, can't open the specified file
simpleRead.ReadSomething("/home/ivan/Documents/crashsite/node-go-crossfire/simpletext.txt")
I'm running it on Ubuntu 18.04 64bit.
Remember that strings in Go are like slices. They are composed of a pointer to the backing data and the length. This is why in your code, GoString is defined as:
var GoString = Struct({
p: "string", // pointer
n: "longlong" // length
});
I'd recommend you define a function for creating a GoString e.g.
function NewGoString(str) {
return new GoString({p: str, n: str.length})
}
Which you can use in your code like:
var simpleRead = ffi.Library("./simpleread.so", {
ReadSomething: ["void", [GoString]]
});
simpleRead.ReadSomething(NewGoString("/path/to/your/file"))

Using the Linux sysfs_notify call

I am trying to communicate asynchronously between a kernel driver and a user-space program (I know there are lots of questions here that ask for similar information, but I could find none that deal with sysfs_notify).
I am leaving Vilhelm's edit here, but adding the source to both a simple driver utilizing sysfs and a user-space program to poll it. The driver works fine (I got most of it from the net; it is missing the credits, but I couldn't find them when I went back to add them).
Unfortunately, the polling program does not work. It always returns success immediately. Interestingly, if I don't perform the two reads prior to the poll, the revents members are set to POLLERR | POLLIN instead of just POLLIN as seen in the program output.
Program output:
root#ubuntu:/home/wmulcahy/demo# ./readhello
triggered
Attribute file value: 74 (t) [0]
revents[0]: 00000001
revents[1]: 00000001
Here is the driver: hello.c (you can see where I started out...)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
struct my_attr {
struct attribute attr;
int value;
};
static struct my_attr notify = {
.attr.name="notify",
.attr.mode = 0644,
.value = 0,
};
static struct my_attr trigger = {
.attr.name="trigger",
.attr.mode = 0644,
.value = 0,
};
static struct attribute * myattr[] = {
&notify.attr,
&trigger.attr,
NULL
};
static ssize_t show(struct kobject *kobj, struct attribute *attr, char *buf)
{
struct my_attr *a = container_of(attr, struct my_attr, attr);
printk( "hello: show called (%s)\n", a->attr.name );
return scnprintf(buf, PAGE_SIZE, "%s: %d\n", a->attr.name, a->value);
}
static struct kobject *mykobj;
static ssize_t store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t len)
{
struct my_attr *a = container_of(attr, struct my_attr, attr);
sscanf(buf, "%d", &a->value);
notify.value = a->value;
printk("sysfs_notify store %s = %d\n", a->attr.name, a->value);
sysfs_notify(mykobj, NULL, "notify");
return sizeof(int);
}
static struct sysfs_ops myops = {
.show = show,
.store = store,
};
static struct kobj_type mytype = {
.sysfs_ops = &myops,
.default_attrs = myattr,
};
static struct kobject *mykobj;
static int __init hello_module_init(void)
{
int err = -1;
printk("Hello: init\n");
mykobj = kzalloc(sizeof(*mykobj), GFP_KERNEL);
if (mykobj) {
kobject_init(mykobj, &mytype);
if (kobject_add(mykobj, NULL, "%s", "hello")) {
err = -1;
printk("Hello: kobject_add() failed\n");
kobject_put(mykobj);
mykobj = NULL;
}
err = 0;
}
return err;
}
static void __exit hello_module_exit(void)
{
if (mykobj) {
kobject_put(mykobj);
kfree(mykobj);
}
printk("Hello: exit\n");
}
module_init(hello_module_init);
module_exit(hello_module_exit);
MODULE_LICENSE("GPL");
And here is the poll program: readhello.c
#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <poll.h>
#define TEST_SYSFS_TRIGGER "/sys/hello/trigger"
#define TEST_SYSFS_NOTIFY "/sys/hello/notify"
int main(int argc, char **argv)
{
int cnt, notifyFd, triggerFd, rv;
char attrData[100];
struct pollfd ufds[2];
// Open a connection to the attribute file.
if ((notifyFd = open(TEST_SYSFS_NOTIFY, O_RDWR)) < 0)
{
perror("Unable to open notify");
exit(1);
}
// Open a connection to the attribute file.
if ((triggerFd = open(TEST_SYSFS_TRIGGER, O_RDWR)) < 0)
{
perror("Unable to open trigger");
exit(1);
}
ufds[0].fd = notifyFd;
ufds[0].events = POLLIN;
ufds[1].fd = triggerFd;
ufds[1].events = POLLIN;
// Someone suggested dummy reads before the poll() call
cnt = read( notifyFd, attrData, 100 );
cnt = read( triggerFd, attrData, 100 );
ufds[0].revents = 0;
ufds[1].revents = 0;
if (( rv = poll( ufds, 2, 10000)) < 0 )
{
perror("poll error");
}
else if (rv == 0)
{
printf("Timeout occurred!\n");
}
else if (ufds[0].revents & POLLIN)
{
printf("triggered\n");
cnt = read( notifyFd, attrData, 1 );
printf( "Attribute file value: %02X (%c) [%d]\n", attrData[0], attrData[0], cnt );
}
printf( "revents[0]: %08X\n", ufds[0].revents );
printf( "revents[1]: %08X\n", ufds[1].revents );
close( triggerFd );
close( notifyFd );
}
Some upcoming sysfs enhancements.
Internally, the patch adds a wait queue head to every kobject on the
system; that queue is inserted into a poll table in response to a
poll() call. The sysfs code has no way of knowing, however, when the
value of any given sysfs attribute has changed, so the subsystem
implementing a pollable attribute must make explicit calls to:
void sysfs_notify(struct kobject *kobj, char *dir, char *attr);
Thanks,
Lee
The blocking poll is from the user side. User code can simply tell the kernel which attributes it's interested in, then block in a poll() until one of them has changed.
The sysfs_notify() is a kernel-side call that releases the user-space poll(). After you adjust your kernel attribute value, just call sysfs_notify() to allow any user-space applications to respond to their outstanding poll().
Think of the poll() as "subscribing" to notices of a change in an attribute of interest, and sysfs_notify() as "publishing" the change to any subscribers.

How do I use ioctl() to manipulate my kernel module?

So I'm trying to write a kernel module that uses the linux/timer.h file. I got it to work inside just the module, and now I am trying to get it to work from a user program.
Here is my kernel module:
//Necessary Includes For Device Drivers.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/timer.h>
#include <linux/ioctl.h>
#define DEVICE_NAME "mytimer"
#define DEVICE_FILE_NAME "mytimer"
#define MAJOR_NUM 61
#define MINOR_NUM 0
MODULE_LICENSE("Dual BSD/GPL");
static struct timer_list my_timer;
struct file_operations FileOps =
{
//No File Operations for this timer.
};
//Function to perform when timer expires.
void TimerExpire(int data)
{
printk("Timer Data: %d\n", data);
}
//Function to set up timers.
void TimerSetup(void)
{
setup_timer(&my_timer, TimerExpire, 5678);
mod_timer(&my_timer, jiffies + msecs_to_jiffies(5000));
}
//Module Init and Exit Functions.
int init_module(void)
{
int initResult = register_chrdev(MAJOR_NUM, "mytimer", &FileOps);
if (initResult < 0)
{
printk("Cannot obtain major number %d\n", MAJOR_NUM);
return initResult;
}
printk("Loading MyTimer Kernel Module...\n");
return 0;
}
void cleanup_module(void)
{
unregister_chrdev(MAJOR_NUM, "mytimer");
printk("Unloading MyTimer Kernel Module...\n");
}
More specifically, I want my user program to call the TimerSetup() function. I know that I'll need to use ioctl() but I'm not sure how to specify in my MODULE FILE that TimerSetup() should be callable via ioctl().
Also, my second question: I was able to insmod my module and also mknod into /dev/mytimer with the correct major number. But when I tried to open() it so that I can get the file descriptor from it, it kept returning -1, which I'm assuming is wrong. I made sure the permissions were fine (in fact, I made it 777 just to be sure)... It still doesn't work... Is there something I'm missing?
Here is the user program just in case:
#include <stdio.h>
int main(int argc, char* argv[])
{
int fd = open("/dev/mytimer", "r");
printf("fd: %d\n", fd);
return 0;
}
The example code you need can be found in drivers/watchdog/softdog.c (from Linux 2.6.33 at the time this was written), which illustrates proper file operations as well as how to permit userland to fill a structure with ioctl().
It's actually a great, working tutorial for anyone who needs to write trivial character device drivers.
I dissected softdog's ioctl interface when answering my own question, which may be helpful to you.
Here's the gist of it (though far from exhaustive) ...
In softdog_ioctl() you see a simple initialization of struct watchdog_info that advertises functionality, version and device information:
static const struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "Software Watchdog",
};
We then look at a simple case where the user just wants to obtain these capabilities:
switch (cmd) {
case WDIOC_GETSUPPORT:
return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
... which of course, will fill the corresponding userspace watchdog_info with the initialized values above. If copy_to_user() fails, -EFAULT is returned which causes the corresponding userspace ioctl() call to return -1 with a meaningful errno being set.
Note, the magic requests are actually defined in linux/watchdog.h , so that the kernel and userspace share them:
#define WDIOC_GETSUPPORT _IOR(WATCHDOG_IOCTL_BASE, 0, struct watchdog_info)
#define WDIOC_GETSTATUS _IOR(WATCHDOG_IOCTL_BASE, 1, int)
#define WDIOC_GETBOOTSTATUS _IOR(WATCHDOG_IOCTL_BASE, 2, int)
#define WDIOC_GETTEMP _IOR(WATCHDOG_IOCTL_BASE, 3, int)
#define WDIOC_SETOPTIONS _IOR(WATCHDOG_IOCTL_BASE, 4, int)
#define WDIOC_KEEPALIVE _IOR(WATCHDOG_IOCTL_BASE, 5, int)
#define WDIOC_SETTIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 6, int)
#define WDIOC_GETTIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 7, int)
#define WDIOC_SETPRETIMEOUT _IOWR(WATCHDOG_IOCTL_BASE, 8, int)
#define WDIOC_GETPRETIMEOUT _IOR(WATCHDOG_IOCTL_BASE, 9, int)
#define WDIOC_GETTIMELEFT _IOR(WATCHDOG_IOCTL_BASE, 10, int)
WDIOC obviously signifying "Watchdog ioctl"
You can easily take that a step further, having your driver do something and place the result of that something in the structure and copy it to userspace. For instance, if struct watchdog_info also had a member __u32 result_code. Note, __u32 is just the kernel's version of uint32_t.
With ioctl(), the user passes the address of an object, be it a structure, integer, whatever to the kernel expecting the kernel to write its reply in an identical object and copy the results to the address that was provided.
The second thing you are going to need to do is make sure your device knows what to do when someone opens, reads from it, writes to it, or uses a hook like ioctl(), which you can easily see by studying softdog.
Of interest is:
static const struct file_operations softdog_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = softdog_write,
.unlocked_ioctl = softdog_ioctl,
.open = softdog_open,
.release = softdog_release,
};
Where you see the unlocked_ioctl handler going to ... you guessed it, softdog_ioctl().
I think you might be juxtaposing a layer of complexity that really doesn't exist when dealing with ioctl(), it really is that simple. For that same reason, most kernel developers frown on new ioctl interfaces being added unless they are absolutely necessary. Its just too easy to lose track of the type that ioctl() is going to fill vs the magic you use to do it, which is the primary reason that copy_to_user() fails often resulting in the kernel rotting with hordes of userspace processes stuck in disk sleep.
For a timer, I agree, ioctl() is the shortest path to sanity.
You are missing a .open function pointer in your file_operations structure to specify the function to be called when a process attempts to open the device file. You will need to specify a .ioctl function pointer for your ioctl function as well.
Try reading through The Linux Kernel Module Programming Guide, specifically chapters 4 (Character Device Files) and 7 (Talking to Device Files).
Chapter 4 introduces the file_operations structure, which holds pointers to functions defined by the module/driver that perform various operations such as open or ioctl.
Chapter 7 provides information on communicating with a module/drive via ioctls.
Linux Device Drivers, Third Edition is another good resource.
Minimal runnable example
Tested in a fully reproducible QEMU + Buildroot environment, so might help others get their ioctl working. GitHub upstream:
kernel module |
shared header |
userland.
The most annoying part was understanding that some low ids are hijacked: ioctl is not called if cmd = 2 , you have to use _IOx macros.
Kernel module:
#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include "ioctl.h"
MODULE_LICENSE("GPL");
static struct dentry *dir;
static long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long argp)
{
void __user *arg_user;
union {
int i;
lkmc_ioctl_struct s;
} arg_kernel;
arg_user = (void __user *)argp;
pr_info("cmd = %x\n", cmd);
switch (cmd) {
case LKMC_IOCTL_INC:
if (copy_from_user(&arg_kernel.i, arg_user, sizeof(arg_kernel.i))) {
return -EFAULT;
}
pr_info("0 arg = %d\n", arg_kernel.i);
arg_kernel.i += 1;
if (copy_to_user(arg_user, &arg_kernel.i, sizeof(arg_kernel.i))) {
return -EFAULT;
}
break;
case LKMC_IOCTL_INC_DEC:
if (copy_from_user(&arg_kernel.s, arg_user, sizeof(arg_kernel.s))) {
return -EFAULT;
}
pr_info("1 arg = %d %d\n", arg_kernel.s.i, arg_kernel.s.j);
arg_kernel.s.i += 1;
arg_kernel.s.j -= 1;
if (copy_to_user(arg_user, &arg_kernel.s, sizeof(arg_kernel.s))) {
return -EFAULT;
}
break;
default:
return -EINVAL;
break;
}
return 0;
}
static const struct file_operations fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = unlocked_ioctl
};
static int myinit(void)
{
dir = debugfs_create_dir("lkmc_ioctl", 0);
/* ioctl permissions are not automatically restricted by rwx as for read / write,
* but we could of course implement that ourselves:
* https://stackoverflow.com/questions/29891803/user-permission-check-on-ioctl-command */
debugfs_create_file("f", 0, dir, NULL, &fops);
return 0;
}
static void myexit(void)
{
debugfs_remove_recursive(dir);
}
module_init(myinit)
module_exit(myexit)
Shared header between the kernel module and userland:
ioctl.h
#ifndef IOCTL_H
#define IOCTL_H
#include <linux/ioctl.h>
typedef struct {
int i;
int j;
} lkmc_ioctl_struct;
#define LKMC_IOCTL_MAGIC 0x33
#define LKMC_IOCTL_INC _IOWR(LKMC_IOCTL_MAGIC, 0, int)
#define LKMC_IOCTL_INC_DEC _IOWR(LKMC_IOCTL_MAGIC, 1, lkmc_ioctl_struct)
#endif
Userland:
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "../ioctl.h"
int main(int argc, char **argv)
{
int fd, arg_int, ret;
lkmc_ioctl_struct arg_struct;
if (argc < 2) {
puts("Usage: ./prog <ioctl-file>");
return EXIT_FAILURE;
}
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("open");
return EXIT_FAILURE;
}
/* 0 */
{
arg_int = 1;
ret = ioctl(fd, LKMC_IOCTL_INC, &arg_int);
if (ret == -1) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("arg = %d\n", arg_int);
printf("ret = %d\n", ret);
printf("errno = %d\n", errno);
}
puts("");
/* 1 */
{
arg_struct.i = 1;
arg_struct.j = 1;
ret = ioctl(fd, LKMC_IOCTL_INC_DEC, &arg_struct);
if (ret == -1) {
perror("ioctl");
return EXIT_FAILURE;
}
printf("arg = %d %d\n", arg_struct.i, arg_struct.j);
printf("ret = %d\n", ret);
printf("errno = %d\n", errno);
}
close(fd);
return EXIT_SUCCESS;
}

Resources