Writing a User-Space Application to Use IOCTL-Free Linux Device Driver Training in Hyderabad

Writing a User-Space Application to Use IOCTL
Free Embedded Systems Course — Testing Your Linux ioctl Character Driver End to End

← Previous Lecture  |  Next Lecture →

This final lesson of the series completes our free Linux kernel development course module on device control by writing the user-space ioctl application that talks to the character driver we built in the previous lesson. You will build, load, and test the complete Linux ioctl system call round trip on a modern 6.x kernel, and pick up debugging habits that apply to any free Linux device drivers course project.

Linux ioctl user space example Free Embedded Systems Course Device Driver Testing Free Linux Kernel Course

What You Will Learn

  • How to open a device file and call ioctl() correctly from user space
  • How to build and load the matching kernel driver
  • How to verify the round trip with a working test sequence
  • How to debug common ioctl failures like -EFAULT and -ENOTTY

Prerequisites

  • The epdrv character driver and epdrv_ioctl.h header from the previous lesson
  • A Linux 6.x machine or VM with build-essential and kernel headers installed
  • Root or sudo access to insert kernel modules and create device nodes

Step 1: Writing the User-Space IOCTL Application

The user-space side of any Linux ioctl system call interaction is straightforward: open the device file, then call ioctl() with the command and an argument pointer, exactly matching the header shared with the kernel driver.

/* epdrv_test.c - user-space test application */
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include "epdrv_ioctl.h"

int main(void)
{
    int fd, val, ret;

    fd = open("/dev/epdrv0", O_RDWR);
    if (fd < 0) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    val = 42;
    ret = ioctl(fd, EPDRV_SET_VALUE, &val);
    if (ret < 0) {
        perror("ioctl SET_VALUE");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Set value to %d\n", val);

    val = 0;
    ret = ioctl(fd, EPDRV_GET_VALUE, &val);
    if (ret < 0) {
        perror("ioctl GET_VALUE");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Read back value: %d\n", val);

    ret = ioctl(fd, EPDRV_RESET);
    if (ret < 0) {
        perror("ioctl RESET");
        close(fd);
        exit(EXIT_FAILURE);
    }
    printf("Driver value reset\n");

    close(fd);
    return 0;
}

Notice that EPDRV_SET_VALUE and EPDRV_GET_VALUE pass the address of a local variable, while EPDRV_RESET is called with no third argument at all — this matches exactly how each command was defined with _IOW, _IOR, and _IO in the shared header.

Step 2: Building and Loading the Driver

Build the kernel module using a standard out-of-tree Makefile, then load it and confirm the device node was created.

obj-m += epdrv.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
$ make
$ sudo insmod epdrv.ko
$ ls -l /dev/epdrv0
crw------- 1 root root 240, 0 Jul 11 10:00 /dev/epdrv0

Step 3: Building and Running the Test Application

$ gcc -o epdrv_test epdrv_test.c
$ sudo ./epdrv_test
Set value to 42
Read back value: 42
Driver value reset
End-to-End IOCTL Round Trip
epdrv_test
ioctl(SET_VALUE, 42)
→ epdrv driver
stores value = 42
→ epdrv_test
ioctl(GET_VALUE)
→ driver copies
value back

Debugging IOCTL Failures

Symptom Likely Cause Fix
ioctl returns -1, errno = ENOTTY Command number mismatch between app and driver Confirm both sides include the same shared header
ioctl returns -1, errno = EFAULT Invalid or NULL pointer passed as arg Pass the address of a real variable for _IOR/_IOW/_IOWR commands
open() fails with “No such device” Module not loaded or device node missing Check dmesg and lsmod, confirm insmod succeeded
Permission denied opening device Device node permissions default to root only Run test with sudo, or adjust udev rules for the device class

Best Practices for Testing IOCTL Drivers

  • Check the return value of every ioctl() call in user space, never assume success
  • Use dmesg alongside your test app to correlate kernel-side and user-side behavior
  • Write a small test for every defined command, including invalid ones, to confirm -ENOTTY handling
  • Remove the module with rmmod between test runs to catch any cleanup bugs in the exit path

Security Considerations

Even in a simple test driver like this one, remember that any process with permission to open the device node can issue any of its defined ioctl commands. In production drivers, consider whether certain commands should be restricted with capability checks (for example via capable(CAP_SYS_ADMIN)) before performing sensitive operations.

Summary and Key Takeaways

  • User-space ioctl calls must match the kernel driver’s command definitions exactly
  • Always check ioctl() return values and use perror() or errno for diagnostics
  • dmesg is your best friend for correlating kernel-side driver behavior with user-space results
  • Test both valid and invalid commands to confirm your driver’s error handling works as expected

Conclusion

With this lesson, you have completed a full, modern, kernel 6.x-compatible walkthrough of the Linux ioctl system call: why it exists, how to implement it in a character driver, and how to build and test a matching user-space application. These same patterns apply directly to real embedded systems work, whether you are configuring a sensor, a serial port, or a custom hardware accelerator. Continue exploring more lessons in this free Linux kernel development course to build on these foundations.

Frequently Asked Questions

Q1. Why does my ioctl() call return -1 with errno set to ENOTTY?

This usually means the command number the app sent doesn’t match anything the driver recognizes — double-check that both sides include the identical shared header.

Q2. Do I need to call ioctl() with a pointer even for commands that don’t transfer data?

No. Commands defined with the plain _IO() macro, like EPDRV_RESET, take no third argument.

Q3. Why does my test application need sudo to run?

By default, device nodes created with device_create() are owned by root with restrictive permissions; you can relax this with udev rules if needed.

Q4. How can I see what my driver is doing during an ioctl call?

Add pr_info() or dev_info() calls inside the handler and watch them with dmesg -w while running your test application.

Q5. Should production drivers restrict who can call certain ioctl commands?

Yes, for sensitive operations. Use capability checks like capable(CAP_SYS_ADMIN) before performing privileged actions inside the handler.

← Previous Lecture  |  Next Lecture →

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *