本文へ移動
cccskills
無料GitHub で公開

device-drivers

Use when writing or debugging Linux drivers: platform, I2C/SPI, device-tree, char-device, IRQ, DMA, regmap, PM, sysfs, udev, NACK, -EREMOTEIO, or probe failures.

インストール方法を見る

含まれるファイル(2)

  • SKILL.md22.5 KB
  • agents/openai.yaml229 B

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Linux device drivers

Contract

FieldBound contract
TriggerWriting or debugging a Linux device driver in platform, I2C, or SPI form, including its device-tree description, char-device interface, threaded IRQ, DMA, regmap, runtime PM, sysfs, deferred-probe, or udev path.
AuthorityReversible local: writes only driver source, Kconfig and Makefile entries, DTS/DTSI, binding and overlay files, and udev rule files inside the project tree; rollback is version control. No remote mutation.
Side effectLocal edits to driver, build, device-tree, binding, overlay, and udev files; guidance and debug commands in chat.
DoneThe device description matches its hardware and driver table, the driver probes and unbinds cleanly, char, IRQ, DMA, bus, and PM paths use the correct APIs, and every observed symptom maps to one checked cause.

Inputs

  1. Driver code or symptom (required): source under review, a probe failure, an IRQ storm, a missing /dev node, NACK or -EREMOTEIO, or DMA corruption.
  2. Bus and device origin (optional): platform, i2c, or spi; the device-tree compatible string, ACPI id, or legacy board-file entry; and the adapter address or SPI chip-select.
  3. Hardware facts (optional): MMIO base and length, IRQ and trigger, clocks, regulators, bus placement, and the register map or datasheet. Supply these when creating a device-tree node or a regmap.
  4. Device-tree context (conditional): the SoC .dtsi, board .dts, binding document, phandles, overlay and boot chain, or a failure report such as a driver that never probes or an IRQ that never fires.
  5. Interface contract (conditional): requested read, write, ioctl, mmap, or poll operations, plus the userspace ioctl header and struct layout.
  6. Transfer shape (conditional): register and value widths, repeated-start requirements, SPI mode and word size, and buffer lifetime or DMA constraints.
  7. Kernel version (optional): mainline 7.2 or LTS 6.18 is assumed when it is not stated.
  8. Mode (optional): device-tree, platform-device-model, bus-drivers-i2c-spi, or writing-char-drivers; choose one branch or combine branches when one driver spans them. Done when: the selected mode and its required inputs are recorded.

Procedure

  1. Name the bus, the device origin, and the kernel. Confirm whether the device comes from device tree, ACPI, or a legacy board file, and read uname -r on the target. State the driver-model hierarchy: a bus_type such as platform, AMBA, PCI, I2C, or SPI carries struct device instances, each binds to at most one struct device_driver, and the core calls probe on a match and remove at unbind. Done when: the bus, origin, kernel version, and matching model are recorded.

  2. Device-tree mode applies when the device has a DTS/DTSI, binding, phandle, overlay, or OF probe problem.

    a. Lay out the node under its bus parent. A board file includes the SoC .dtsi and extends or references its nodes by label. Keep #address-cells and #size-cells from the parent in view.

    /dts-v1/;
    #include "soc.dtsi"
    
    / {
        model = "My Board";
        compatible = "vendor,my-board", "vendor,soc-family";
    };
    
    &i2c1 {
        sensor: sensor@48 {
            compatible = "vendor,sensor";
            reg = <0x48>;
        };
    };
    

    Done when: the node is under the right bus parent and every reg cell count matches the parent's address and size cells.

    b. Set the load-bearing properties from the datasheet or binding: compatible strings most-specific first; reg address and length; interrupts using the interrupt parent's cells; clocks and clock-names; and status, where "disabled" keeps the device unprobed. Done when: each property value traces to a hardware fact or binding rule.

    c. Wire phandles for cross-node references. A phandle is the label reference the compiler resolves to a number.

    clk_sensor: clock-sensor {
        compatible = "fixed-clock";
        #clock-cells = <0>;
    };
    
    &sensor {
        clocks = <&clk_sensor>;
        clock-names = "apb_pclk";
    };
    

    Done when: every &label reference resolves to a node in the compiled tree.

    d. Match the driver to the node. The OF core parses the DTB at boot; of_platform_populate() creates platform_device instances for bus nodes, and a driver binds when its of_match_table matches compatible. For I2C or SPI children, make the child compatible match an of_device_id entry and make reg equal the datasheet address.

    static const struct of_device_id my_of_match[] = {
        { .compatible = "vendor,sensor" },
        { }
    };
    MODULE_DEVICE_TABLE(of, my_of_match);
    

    Done when: the driver's match table names the node's exact compatible string and the child address is correct.

    e. Compile and inspect the tree with dtc or make dtbs, and name the binding document under Documentation/devicetree/bindings/ or its YAML dt-schema path when a binding changes.

    dtc -I dts -O dtb -o board.dtb board.dts
    dtc -I fs -O dts /proc/device-tree | less
    ls /sys/firmware/devicetree/base/
    

    Done when: the compiled DTB matches the running tree, or the delta explains the failure.

    f. Apply overlays only through a supported boot chain. CONFIG_OF_OVERLAY provides kernel overlay support; mainline normally applies overlays through U-Boot's fdt apply or merges a .dtbo with fdtoverlay at build time. The configfs path /sys/kernel/config/device-tree/overlays/ exists only on kernels carrying the vendor CONFIG_OF_CONFIGFS patch. Overlay resolution failures name unresolved __fixups__; export target labels as __symbols__ in the base DTB. Done when: the apply path is named for the actual kernel and boot chain.

    g. Diagnose an OF probe failure bottom-up: confirm the node is in /proc/device-tree, confirm status is "okay", compare compatible character for character, then confirm clocks, regulators, and phys are available or the driver returns -EPROBE_DEFER. Done when: the first broken link in the chain is named.

  3. Use platform mode for platform-driver lifecycle, properties, sysfs, and deferred probe. Build probe and remove around managed resources. Allocate private state with devm_kzalloc, look up the resource, map it with devm_ioremap_resource or devm_platform_ioremap_resource, stash it with platform_set_drvdata, and register with module_platform_driver. remove returns void on the supported kernel floor; leave it empty when every resource is devm_*-managed.

    static int my_probe(struct platform_device *pdev)
    {
        struct my_priv *priv;
        struct resource *res;
        void __iomem *base;
    
        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
        if (!priv)
            return -ENOMEM;
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        base = devm_ioremap_resource(&pdev->dev, res);
        if (IS_ERR(base))
            return PTR_ERR(base);
        platform_set_drvdata(pdev, priv);
        return 0;
    }
    
    static void my_remove(struct platform_device *pdev)
    {
        /* devm_* resources are freed by the core after this returns */
    }
    
    static struct platform_driver my_pdrv = {
        .probe = my_probe,
        .remove = my_remove,
        .driver = {
            .name = "my-device",
            .of_match_table = my_of_match,
        },
    };
    module_platform_driver(my_pdrv);
    

    Read DT and ACPI properties through the unified property API, using of_* only for DT-specific semantics. Expose state through DEVICE_ATTR_RO or DEVICE_ATTR_RW, format with sysfs_emit, and list attributes in .dev_groups so the core creates and removes them at bind and unbind. Return -EPROBE_DEFER when a required supplier is not ready and inspect /sys/kernel/debug/devices_deferred to see who is waiting on whom. Done when: the skeleton has resource lookup, a managed remap, private-data storage, and registration; each property read handles its error; sysfs cannot leak after unbind; and every supplier dependency has a deliberate ordering or deferred return.

    static int my_read_properties(struct device *dev)
    {
        u32 speed;
        bool has_feature;
        int ret;
    
        ret = device_property_read_u32(dev, "clock-speed", &speed);
        if (ret)
            return ret;
        has_feature = device_property_present(dev, "feature-x");
        dev_dbg(dev, "clock speed %u, feature-x %s\n", speed,
                has_feature ? "present" : "absent");
        return 0;
    }
    

    Debug platform binding from the running system:

    ls /sys/bus/platform/devices/
    ls /sys/bus/platform/drivers/
    cat /sys/kernel/debug/devices_deferred
    udevadm monitor
    

    Done when: the device and driver directories, deferred dependency list, and relevant uevents have been inspected for the reported symptom.

  4. Use I2C/SPI mode for client registration, transfers, bus bindings, and NACK diagnosis. The bus core owns the adapter or controller; the client driver owns the device registers. For I2C, carry both match tables, a void remove, and module_i2c_driver.

    static void my_remove(struct i2c_client *client) {}
    
    static struct i2c_driver my_driver = {
        .probe = my_probe,
        .remove = my_remove,
        .driver = {
            .name = "mysensor",
            .of_match_table = my_of_id,
        },
        .id_table = my_id,
    };
    module_i2c_driver(my_driver);
    

    Prefer regmap for ordinary register access. Set reg_bits, val_bits, and max_register from the datasheet, initialize with devm_regmap_init_i2c or the SPI equivalent, and use regmap_read, regmap_write, and regmap_update_bits. Use a raw i2c_transfer only for a protocol regmap cannot express, such as a write followed by a repeated-start read; check that the return value equals the message count.

    static int my_read_reg(struct i2c_client *client)
    {
        u8 reg = 0x0F, val;
        int ret;
        struct i2c_msg msgs[] = {
            { .addr = client->addr, .flags = 0,        .len = 1, .buf = &reg },
            { .addr = client->addr, .flags = I2C_M_RD, .len = 1, .buf = &val },
        };
    
        ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
        if (ret != ARRAY_SIZE(msgs))
            return ret < 0 ? ret : -EIO;
        return 0;
    }
    

    For SPI, set mode, word size, and transfer settings from the datasheet, call spi_setup, build a spi_message, call spi_sync, check its return, and register with module_spi_driver. Keep bulk-transfer buffers DMA-safe and kmalloced rather than on the stack. The SPI core may bounce small or non-DMA-safe buffers, but that adds a copy.

    static int spi_probe(struct spi_device *spi)
    {
        int ret;
        u8 tx[2] = { 0 };
        u8 rx[2];
        struct spi_transfer transfer = {
            .tx_buf = tx,
            .rx_buf = rx,
            .len = ARRAY_SIZE(tx),
        };
        struct spi_message message;
    
        spi->mode = SPI_MODE_0;
        spi->bits_per_word = 8;
        ret = spi_setup(spi);
        if (ret)
            return ret;
        spi_message_init(&message);
        spi_message_add_tail(&transfer, &message);
        ret = spi_sync(spi, &message);
        if (ret)
            return ret;
        return 0;
    }
    

    Debug the real bus without a driver loaded when possible: i2cdetect -y 1 scans ACKs, i2cdump -y 1 0x48 dumps registers, and /sys/bus/i2c/devices/i2c-1/1-0048/name identifies a bound device. Done when: registration returns are checked, the binding and address match the datasheet, each access uses the right mechanism, and the reported address, wiring, transfer, or binding cause is confirmed.

  5. Use char-device mode for a userspace interface. Allocate a dynamic major, initialize and add the cdev, create the class and device node, and unwind every earlier resource on failure. Check every registration call. Inside a platform driver, prefer the corresponding devm_* variants and register from probe.

    static dev_t devno;
    static struct cdev my_cdev;
    static struct class *my_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 stays unset: an unset field means no seek support */
    };
    
    static int __init my_init(void)
    {
        int ret;
        struct device *my_device;
    
        ret = alloc_chrdev_region(&devno, 0, 1, "mydev");
        if (ret)
            return ret;
        cdev_init(&my_cdev, &my_fops);
        ret = cdev_add(&my_cdev, devno, 1);
        if (ret)
            goto err_region;
        my_class = class_create("mydev");
        if (IS_ERR(my_class)) {
            ret = PTR_ERR(my_class);
            goto err_cdev;
        }
        my_device = device_create(my_class, NULL, devno, NULL, "mydev");
        if (IS_ERR(my_device)) {
            ret = PTR_ERR(my_device);
            goto err_class;
        }
        return 0;
    
    err_class:
        class_destroy(my_class);
    err_cdev:
        cdev_del(&my_cdev);
    err_region:
        unregister_chrdev_region(devno, 1);
        return ret;
    }
    static void __exit my_exit(void)
    {
        device_destroy(my_class, devno);
        class_destroy(my_class);
        cdev_del(&my_cdev);
        unregister_chrdev_region(devno, 1);
    }
    
    module_init(my_init);
    module_exit(my_exit);
    

    Copy kernel and user memory only with copy_to_user and copy_from_user; their nonzero return is the number of bytes not copied and becomes -EFAULT. They can sleep, so hold no spinlock around them. Initialize every byte of a stack buffer before copying and advance *ppos only after a full copy.

    Define ioctl commands with _IO, _IOR, _IOW, or _IOWR, which encode magic, number, direction, and data size. Use a fixed-size struct for _IOWR, provide compat_ioctl when pointer-carrying commands serve 32-bit callers on a 64-bit kernel, and share one command header between kernel and userspace. Return -ENOTTY for an unknown command.

    Validate a requested device-memory window and caller permissions before remap_pfn_range; set pgprot_noncached for MMIO. For a DMA buffer, validate the size and permissions before mapping. Since kernel 6.12 removed no_llseek, leave .llseek unset for a non-seekable device. Since kernel 6.4, class_create takes only the class name; older kernels expected the module argument.

    Support blocking reads with a wait queue: .poll calls poll_wait, new data calls wake_up_interruptible, and async readers use fasync_helper for SIGIO. Done when: the dynamic registration has a checked cleanup ladder, all user copies are fault-safe, ioctl and compat contracts are shared, mmap bounds are checked before mapping, non-seekability uses an unset field, and every blocking site has a wake source.

  6. Choose the IRQ pattern from the work the handler does.

    PatternUse when
    Hard IRQ onlyMicrosecond work, nothing that sleeps
    Threaded IRQI2C/SPI reads, mutexes, scheduling work
    IRQF_ONESHOTLevel-triggered line: mask until the thread returns
    return devm_request_threaded_irq(dev, irq, my_hardirq, my_threaded,
                                     IRQF_ONESHOT, "mydev", dev);
    

    The hard IRQ acknowledges and returns IRQ_WAKE_THREAD; the thread does the sleeping work and returns IRQ_HANDLED. Done when: sleeping work never runs in hard context and the acknowledge occurs in the hard IRQ.

  7. Map DMA at the right coherency. Use a coherent mapping when CPU and device share a buffer continuously. Use streaming mappings for existing buffers and sync at every direction change.

    cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
    dma = dma_map_single(dev, buf, size, DMA_FROM_DEVICE);
    dma_sync_single_for_device(dev, dma, size, DMA_FROM_DEVICE);
    /* device writes */
    dma_sync_single_for_cpu(dev, dma, size, DMA_FROM_DEVICE);
    dma_unmap_single(dev, dma, size, DMA_FROM_DEVICE);
    

    Done when: every streaming mapping has a sync before each ownership transfer and exactly one unmap.

  8. Route register access through regmap. Declare register and value widths and max_register; read and write through regmap_read, regmap_write, and regmap_update_bits. Regmap owns locking, caching, and bulk access, so ordinary I2C and SPI drivers do not bypass it with raw i2c_smbus_* calls. Done when: no ordinary driver path bypasses its regmap instance.

  9. Gate clocks through runtime PM. Fill runtime_suspend and runtime_resume in dev_pm_ops, enable PM with devm_pm_runtime_enable, and power up with pm_runtime_resume_and_get() rather than pm_runtime_get_sync(), because the former drops the usage count again when resume fails. Done when: every get has a matching put on both success and error paths.

  10. Add the udev rule for the device node.

# /etc/udev/rules.d/99-mydev.rules
KERNEL=="mydev", MODE="0666", GROUP="plugdev"

sudo udevadm control --reload-rules && sudo udevadm trigger
udevadm info -a -n /dev/mydev

Done when: after a reload, the node carries the intended mode and group.

  1. Review the finished driver against the failure table. For each reported symptom, run the relevant DT, platform, bus, char, IRQ, DMA, PM, or udev check and record the evidence. Done when: every finding names one failure-table row and every requested operation has reviewed code or a named reason it is not applicable.

Failure and recovery

SymptomCauseRecovery
probe returns -EBUSYResource conflict, duplicate node, or double probeCheck the device-tree status, verify one driver per device and one owner per region, and use devm_* cleanup.
IRQ stormMissing acknowledgeACK in the hard IRQ and use IRQF_ONESHOT on level lines.
Device reads stale DMA dataMissing sync at an ownership changeCall dma_sync_single_for_cpu or _for_device at each boundary.
/dev node missingcdev_add, class_create, or device_create failedCheck each return, unwind in reverse, and inspect dmesg.
copy_to_user or copy_from_user returns a faultInvalid or faulting user pointerRely on the copy function's return value, optionally use access_ok as an early filter, and return -EFAULT.
Runtime PM hangGet/put imbalanceUse pm_runtime_resume_and_get and pair every successful get with a put.
-EREMOTEIO on transferNACK from a wrong address or missing pull-upsRun i2cdetect, check the DT reg, and inspect the wiring.
Garbled SPI dataCPOL/CPHA mismatchSet spi->mode from the datasheet timing diagram.
Probe never runs or driver never bindscompatible typo, missing child node, or wrong match tableMatch the exact string, confirm the node sits under the right bus, and inspect the running tree.
Regmap returns -EIOreg_bits or val_bits disagrees with the chipFix the widths in regmap_config.
Probe deferred foreverClock, regulator, PHY, or other supplier is absent or not readyReturn -EPROBE_DEFER, inspect devices_deferred, and repair or enable the supplier chain.
Slow bulk SPITransfer buffers are not DMA-safeAllocate transfer buffers with kmalloc, not on the stack.
Wrong MMIO decodedParent #address-cells or #size-cells mismatchFollow the SoC .dtsi parent conventions.
IRQ never firesWrong interrupt parent or cell shapeCopy the specifier shape from a working node on the same controller.
Overlay fails to applyUnresolved symbolsExport target labels as __symbols__ in the base DTB and inspect __fixups__.
Major number collisionFixed major already takenRegister with alloc_chrdev_region.
-ENOTTY in userspaceIoctl magic or number mismatchBuild both sides from one shared command header and return -ENOTTY only for unknown commands.
SIGSEGV in an mmap regionCached mapping over device memory or an unchecked windowSet pgprot_noncached and reject a length or permission outside the device window before remap_pfn_range.
scheduling while atomicSleeping call under a spinlock in an operationRelease the lock before any call that can sleep, including user-copy functions.
Late init failureclass_create or device_create failedUnwind in reverse: cdev_del, device_destroy, class_destroy, and unregister_chrdev_region.
no_llseek undeclaredKernel 6.12 removed the symbolDelete the .llseek assignment; an unset field means no seeking.
class_create argument errorKernel 6.4 dropped the module argumentPass the class name as the single argument.
Node permissions revert on replugThe udev rule does not matchPrint the match chain with udevadm info -a and fix the key.
Failure classBehavior
Probe fails after partial setupAudit the cleanup ladder: every non-devm resource acquired after a jump target is released before returning the error.
Sleeping call in hard IRQMove the work to the threaded handler; the hard IRQ only acknowledges.
DMA corruption persistsRe-check direction flags and ownership transitions; a DMA_FROM_DEVICE mapping is synchronized for device before the device writes and for CPU after it completes.
Node permissions revert on replugThe rule does not match; print the match chain with udevadm info -a and fix the key.

Output

  1. Device-tree node or board fragment, property table with cell arithmetic, phandle wiring, binding path, match snippet, compile and inspect transcript, and overlay path when the DT mode applies.
  2. Device origin, driver-model relationship, managed probe/remove skeleton, unified property reads, sysfs attribute, deferred-probe evidence, and platform debug transcript when the platform mode applies.
  3. I2C or SPI client skeleton, child node and match table, regmap configuration or checked raw transfer, SPI settings with DMA-safe buffers, and real-bus debug transcript when the bus mode applies.
  4. Char-device registration and cleanup ladder, user-copy, ioctl and compat, mmap, and poll implementation when the char mode applies.
  5. IRQ, DMA, runtime-PM, and udev wiring matched to hardware behavior.
  6. Symptom findings tied to the failure table, with causes actually checked on the target.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Use when explaining System V AMD64, ARM AAPCS, RISC-V psABI, stack frames, variadic calls, or FFI register rules. Not for the Rust FFI binding layer: use rust-ffi.

日本語の概要は準備中です。原文の説明を表示しています。

OutlineDriven/odin-claude-plugin372026年9月29日 更新

Use when configuring ADC sampling time, DMA-driven ADC, calibration, or DAC channel setup on bare-metal MCUs. Not for the DMA stream itself: use dma-baremetal.

日本語の概要は準備中です。原文の説明を表示しています。

OutlineDriven/odin-claude-plugin372026年9月29日 更新

af-xdp

無料

Use when creating AF_XDP sockets, configuring UMEM and XSK rings, writing an XDP redirect program, or choosing copy versus zero-copy mode. Not for full kernel bypass: use dpdk.

日本語の概要は準備中です。原文の説明を表示しています。

OutlineDriven/odin-claude-plugin372026年9月29日 更新

Use when a completed session needs an agent-environment retrospective. Not for an engineering retrospective from telemetry: use engineering-retrospective.

日本語の概要は準備中です。原文の説明を表示しています。

OutlineDriven/odin-claude-plugin372026年9月29日 更新

Use when a redacted, trimmed agent transcript must be appended to a GitHub PR or issue body, with human approval and preview. Not for automated or model-initiated insertion.

日本語の概要は準備中です。原文の説明を表示しています。

OutlineDriven/odin-claude-plugin372026年9月29日 更新

agents-md

無料

Use when a repo needs agent setup, AGENTS.md added or made lean, CLAUDE.md audited, or agent instructions scored or pruned. Not for remote, credential, publish, deploy, or irreversible changes.

日本語の概要は準備中です。原文の説明を表示しています。

OutlineDriven/odin-claude-plugin372026年9月29日 更新

OutlineDriven のスキルをすべて見る

このスキルの問題を報告する