/writing-char-drivers
Character driver skill for Linux kernel char devices. Use when implementing file_operations, cdev, copy_to_user, ioctl, or mmap for userspace interfaces. Activates on queries about char device, cdev, file_operations, copy_from_user, ioctl, or kernel mmap.
$ npx -y skills add mohitmishra786/low-level-dev-skills --skill writing-char-drivers --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/writing-char-drivers
Context preview
The summary Claude sees to decide when to auto-load this skill.
Character driver skill for Linux kernel char devices. Use when implementing file_operations, cdev, copy_to_user, ioctl, or mmap for userspace interfaces. Activates on queries about char device, cdev, file_operations, copy_from_user, ioctl, or kernel mmap.
SKILL.md
writing-char-drivers.SKILL.mdname: writing-char-drivers
description: Character driver skill for Linux kernel char devices. Use when implementing file_operations, cdev, copy_to_user, ioctl, or mmap for userspace interfaces. Activates on queries about char device, cdev, file_operations, copy_from_user, ioctl, or kernel mmap.
Writing Character Drivers
Purpose
Guide agents through Linux character device implementation: `struct file_operations`, `cdev` registration, safe userspace copies, `ioctl` design, and basic `mmap` — focused depth beyond `skills/kernel/device-drivers`.
When to Use
- Exposing hardware to `/dev/mydev`
- Implementing `read`/`write`/`poll` from kernel
- Defining `ioctl` commands with type-safe macros
- Mapping device MMIO to userspace (carefully)
Workflow
1. Char device registration
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define MY_MAJOR 0 /* 0 = dynamic alloc */
#define MY_MINOR 0
static dev_t devno;
static struct cdev my_cdev;
static struct class *class;
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
.llseek = no_llseek,
};
static int __init my_init(void)
{
int ret = alloc_chrdev_region(&devno, MY_MINOR, 1, "mydev");
if (ret)
return ret;
cdev_init(&my_cdev, &my_fops);
ret = cdev_add(&my_cdev, devno, 1);
if (ret)
goto err_cdev;
class = class_create("mydev");
device_create(class, NULL, devno, NULL, "mydev");
return 0;
err_cdev:
unregister_chrdev_region(devno, 1);
return ret;
}Modern drivers often use `devm_*` variants inside `probe`.
2. Safe userspace I/O
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char kbuf[128];
ssize_t len;
if (*ppos >= sizeof(kbuf))
return 0;
len = min(count, sizeof(kbuf) - *ppos);
memcpy(kbuf, "data", 4);
if (copy_to_user(buf, kbuf + *ppos, len))
return -EFAULT;
*ppos += len;
return len;
}Never dereference `__user` pointers directly.
3. ioctl pattern
#include <linux/ioctl.h>
#define MY_IOC_MAGIC 'k'
#define MY_IOC_RESET _IO(MY_IOC_MAGIC, 0)
#define MY_IOC_SET _IOW(MY_IOC_MAGIC, 1, int)
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case MY_IOC_RESET:
return 0;
case MY_IOC_SET: {
int val;
if (copy_from_user(&val, (void __user *)arg, sizeof(val)))
return -EFAULT;
return 0;
}
default:
return -ENOTTY;
}
}Use `_IOWR` with fixed-size structs; prefer `compat_ioctl` on bi-arch.
4. mmap (device memory)
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long size = vma->vm_end - vma->vm_start;
phys_addr_t phys = device_phys_base;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT,
size, vma->vm_page_prot);
}Prefer `mmap` of DMA buffers only with explicit size limits and permission checks.
5. poll / async I/O
Implement `poll` + `wake_up_interruptible` for blocking reads; use `fasync_helper` for SIGIO.
6. Agent usage
/writing-char-drivers Add unlocked_ioctl SET_SPEED to existing platform driver
Common Problems
| Symptom | Cause | Fix | |---------|-------|-----| | `-EFAULT` | Bad user pointer | Validate `access_ok` (older) / rely on `copy_*` | | `ENOTTY` | Wrong ioctl magic | Match userspace `ioctl.h` | | Major conflict | Static major taken | Use `alloc_chrdev_region` | | mmap SIGSEGV | Cached mapping to device | `pgprot_noncached` | | Sleep in ioctl | Holding spinlock | Drop lock before blocking |
Related Skills
- `skills/kernel-dev/platform-device-model` — probe context
- `skills/kernel/device-drivers` — full driver lifecycle
- `skills/kernel/kernel-concurrency` — locking in file ops
- `skills/kernel-dev/kernel-debugging-advanced` — trace ioctl path
- `skills/low-level-programming/linux-kernel-modules` — module boilerplate
Read more
name: writing-char-drivers description: Character driver skill for Linux kernel char devices. Use when implementing file_operations, cdev, copy_to_user, ioctl, or mmap for userspace interfaces. Activates on queries about char device, cdev, file_operations, copy_from_user, ioctl, or kernel mmap.
Writing Character Drivers
Purpose
Guide agents through Linux character device implementation: `struct file_operations`, `cdev` registration, safe userspace copies, `ioctl` design, and basic `mmap` — focused depth beyond `skills/kernel/device-drivers`.
When to Use
- Exposing hardware to `/dev/mydev`
- Implementing `read`/`write`/`poll` from kernel
- Defining `ioctl` commands with type-safe macros
- Mapping device MMIO to userspace (carefully)
Workflow
1. Char device registration
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define MY_MAJOR 0 /* 0 = dynamic alloc */
#define MY_MINOR 0
static dev_t devno;
static struct cdev my_cdev;
static struct class *class;
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.unlocked_ioctl = my_ioctl,
.llseek = no_llseek,
};
static int __init my_init(void)
{
int ret = alloc_chrdev_region(&devno, MY_MINOR, 1, "mydev");
if (ret)
return ret;
cdev_init(&my_cdev, &my_fops);
ret = cdev_add(&my_cdev, devno, 1);
if (ret)
goto err_cdev;
class = class_create("mydev");
device_create(class, NULL, devno, NULL, "mydev");
return 0;
err_cdev:
unregister_chrdev_region(devno, 1);
return ret;
}Modern drivers often use `devm_*` variants inside `probe`.
2. Safe userspace I/O
static ssize_t my_read(struct file *filp, char __user *buf,
size_t count, loff_t *ppos)
{
char kbuf[128];
ssize_t len;
if (*ppos >= sizeof(kbuf))
return 0;
len = min(count, sizeof(kbuf) - *ppos);
memcpy(kbuf, "data", 4);
if (copy_to_user(buf, kbuf + *ppos, len))
return -EFAULT;
*ppos += len;
return len;
}Never dereference `__user` pointers directly.
3. ioctl pattern
#include <linux/ioctl.h>
#define MY_IOC_MAGIC 'k'
#define MY_IOC_RESET _IO(MY_IOC_MAGIC, 0)
#define MY_IOC_SET _IOW(MY_IOC_MAGIC, 1, int)
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case MY_IOC_RESET:
return 0;
case MY_IOC_SET: {
int val;
if (copy_from_user(&val, (void __user *)arg, sizeof(val)))
return -EFAULT;
return 0;
}
default:
return -ENOTTY;
}
}Use `_IOWR` with fixed-size structs; prefer `compat_ioctl` on bi-arch.
4. mmap (device memory)
static int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
unsigned long size = vma->vm_end - vma->vm_start;
phys_addr_t phys = device_phys_base;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
return remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT,
size, vma->vm_page_prot);
}Prefer `mmap` of DMA buffers only with explicit size limits and permission checks.
5. poll / async I/O
Implement `poll` + `wake_up_interruptible` for blocking reads; use `fasync_helper` for SIGIO.
6. Agent usage
/writing-char-drivers Add unlocked_ioctl SET_SPEED to existing platform driver
Common Problems
| Symptom | Cause | Fix | |---------|-------|-----| | `-EFAULT` | Bad user pointer | Validate `access_ok` (older) / rely on `copy_*` | | `ENOTTY` | Wrong ioctl magic | Match userspace `ioctl.h` | | Major conflict | Static major taken | Use `alloc_chrdev_region` | | mmap SIGSEGV | Cached mapping to device | `pgprot_noncached` | | Sleep in ioctl | Holding spinlock | Drop lock before blocking |
Related Skills
- `skills/kernel-dev/platform-device-model` — probe context
- `skills/kernel/device-drivers` — full driver lifecycle
- `skills/kernel/kernel-concurrency` — locking in file ops
- `skills/kernel-dev/kernel-debugging-advanced` — trace ioctl path
- `skills/low-level-programming/linux-kernel-modules` — module boilerplate
A curated suite of AI agent skills for systems and low-level programming — C/C++, Rust, Zig, GPU, bare-metal firmware, Linux kernel/driver development, computer architecture, compiler internals, HPC, and more.
Repo: mohitmishra786/low-level-dev-skills
Other skills on low-level-dev-skills.
- /custom-allocators
Custom allocator skill for memory allocation strategies. Use when implementing pool/slab/arena allocators, tuning jemalloc/mimalloc, writing Rust GlobalAlloc, or benchmarking allocator performance. Activates on queries about jemalloc, mimalloc, tcmalloc, arena allocator,
Open skill - /numa-programming
NUMA programming skill for multi-socket memory locality. Use when detecting NUMA topology, binding processes with numactl, using libnuma API, building NUMA-aware data structures, or measuring remote access penalties. Activates on queries about numactl, libnuma, NUMA topology,
Open skill - /af-xdp
AF_XDP skill for high-performance XDP sockets. Use when creating AF_XDP sockets, configuring UMEM and XSK rings, XDP_REDIRECT programs, copy vs zero-copy mode, or comparing with DPDK. Activates on queries about AF_XDP, xsk_umem, XDP_REDIRECT, libbpf xsk, or zero-copy XDP.
Open skill - /dpdk
DPDK skill for userspace packet I/O. Use when initializing EAL, configuring PMD drivers, using mbuf pools and rte_ring, setting up huge pages, RSS, or testpmd validation. Activates on queries about DPDK, EAL, rte_eth_rx_burst, hugepages, PMD, or testpmd.
Open skill - /io-uring
io_uring skill for Linux async I/O. Use when building high-performance servers with liburing, multi-shot operations, provided buffers, fixed files, zero-copy send, or tokio-uring. Activates on queries about io_uring, SQE/CQE, liburing, IORING_OP_PROVIDE_BUFFERS, or io_uring vs
Open skill - /adc-dac-baremetal
Bare-metal ADC and DAC skill. Use when configuring analog sampling, DMA-driven ADC, calibration, or DAC output on MCUs. Activates on queries about ADC bare-metal, sampling time, DMA ADC, or DAC channel setup.
Open skill

