Fundamentals 13 min read

Analyzing KVM Misc Device Initialization and Access on Linux

This article walks through the registration of the KVM misc device in the Linux 5.9 kernel for arm64, explains how /dev/kvm is created via misc_register, and demonstrates a user‑space program that opens the device and uses an ioctl to retrieve the KVM API version, with detailed code analysis.

Linux Kernel Journey
Linux Kernel Journey
Linux Kernel Journey
Analyzing KVM Misc Device Initialization and Access on Linux

The Linux 5.9 kernel for the arm64 architecture registers a KVM miscellaneous character device during the kvm_init function. The device is described by a struct miscdevice named kvm_dev, which points to the file‑operations structure kvm_chardev_ops. Calling misc_register(&kvm_dev) creates the device node /dev/kvm in the /dev directory, making the KVM functionality reachable from user space.

// virt/kvm/kvm_main.c
static struct file_operations kvm_chardev_ops = {
  .unlocked_ioctl = kvm_dev_ioctl,
  .llseek  = noop_llseek,
  KVM_COMPAT(kvm_dev_ioctl),
};

static struct miscdevice kvm_dev = {
  KVM_MINOR,
  "kvm",
  &kvm_chardev_ops,
};

The misc_register implementation (in drivers/char/misc.c) first determines whether the device needs a dynamic minor number. Because KVM_MINOR (232) is predefined, the function skips dynamic allocation, constructs a dev_t with the misc major number, creates the device with device_create_with_groups, and adds the miscdevice to the global misc_list. If a device with the same minor already exists, -EBUSY is returned.

int misc_register(struct miscdevice *misc)
{
  bool is_dynamic = (misc->minor == MISC_DYNAMIC_MINOR);
  // ...
  dev = MKDEV(MISC_MAJOR, misc->minor);
  misc->this_device = device_create_with_groups(..., "%s", misc->name);
  // error handling omitted for brevity
  list_add(&misc->list, &misc_list);
  // unlock and return
}

When a user‑space program opens /dev/kvm, the generic misc character driver invokes misc_open. This function extracts the minor number from the inode, walks misc_list to locate the matching miscdevice, retrieves its file‑operations pointer ( new_fops), stores the device structure in file->private_data, replaces the file's operations with new_fops, and finally calls the open method if it exists. The KVM misc device does not define an open method, so the call returns success without further action.

static int misc_open(struct inode *inode, struct file *file)
{
  int minor = iminor(inode);
  struct miscdevice *c;
  const struct file_operations *new_fops = NULL;
  // lock and search misc_list
  list_for_each_entry(c, &misc_list, list) {
    if (c->minor == minor) {
      new_fops = fops_get(c->fops);
      break;
    }
  }
  file->private_data = c;
  replace_fops(file, new_fops);
  if (file->f_op->open)
    return file->f_op->open(inode, file);
  return 0;
}

The accompanying user‑space program get_version.c demonstrates how to obtain the KVM API version. It opens /dev/kvm, checks for errors such as missing device or unloaded driver, then issues the KVM_GET_API_VERSION ioctl. The returned value is compared with the macro KVM_API_VERSION defined in include/linux/kvm.h (value 12). If they match, the program prints the version.

#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/kvm.h>

#define DEFAULT_KVM_DEV "/dev/kvm"

int main()
{
  int sys_fd = open(DEFAULT_KVM_DEV, O_RDWR);
  if (sys_fd < 0) {
    perror("open /dev/kvm");
    return -1;
  }
  long kvm_api_version = ioctl(sys_fd, KVM_GET_API_VERSION, 0);
  if (kvm_api_version != KVM_API_VERSION) {
    printf("KVM_API_VERSION ioctl
");
    close(sys_fd);
    return -1;
  }
  printf("kvm_api_version = %ld
", kvm_api_version);
  return 0;
}

To build the program, a Makefile sets the cross‑toolchain path for arm64, compiles the source statically, and produces the get_version binary. The binary is placed into a root filesystem, packaged as rootfs.cpio, and launched with QEMU‑system‑aarch64 using a command similar to:

qemu-system-aarch64 \
  -machine virt,gic-version=2,virtualization=on,type=virt \
  -cpu cortex-a57 \
  -smp 1 \
  -m 512M \
  -nographic \
  -kernel linux/arch/arm64/boot/Image \
  -initrd busybox-1.36.1/_install/rootfs.cpio \
  -append "rdinit=/linuxrc console=ttyAMA0"

Running the program inside the virtual machine prints kvm_api_version = 12, confirming that the value obtained through the misc device matches the constant defined in the kernel headers.

In summary, the user‑space ioctl call traverses the following path: open(/dev/kvm)misc_openkvm_chardev_opskvm_dev_ioctl → case KVM_GET_API_VERSION → returns KVM_API_VERSION. This analysis clarifies how the KVM miscellaneous device is initialized, registered, and accessed from user space.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Linux kernelARM64KVMdevice driverioctlmisc device
Linux Kernel Journey
Written by

Linux Kernel Journey

Linux Kernel Journey

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.