driver-know-hows

device driver related stuff

View on GitHub

Chapter 9-11: Advanced Driver Types & Debugging

This file contains the remaining topics: Block Drivers, Network Drivers, and Debugging.


Chapter 9: Block Device Drivers

Overview

Block devices handle data in fixed-size blocks (typically 512, 4096 bytes). Examples: Hard drives, SSDs, RAM disks.

Basic Block Driver Structure

#include <linux/blkdev.h>
#include <linux/blk-mq.h>

struct my_block_dev {
    struct gendisk *disk;
    struct request_queue *queue;
    struct blk_mq_tag_set tag_set;
    u8 *data;           /* RAM disk data */
    size_t size;
};

/* Request handling */
static blk_status_t my_block_request(struct blk_mq_hw_ctx *hctx,
                                     const struct blk_mq_queue_data *bd)
{
    struct request *req = bd->rq;
    struct my_block_dev *dev = req->q->queuedata;
    struct bio_vec bvec;
    struct req_iterator iter;
    loff_t pos = blk_rq_pos(req) << 9;  /* Convert sector to bytes */
    
    blk_mq_start_request(req);
    
    if (blk_rq_is_passthrough(req)) {
        blk_mq_end_request(req, BLK_STS_IOERR);
        return BLK_STS_OK;
    }
    
    rq_for_each_segment(bvec, req, iter) {
        void *buf = kmap_atomic(bvec.bv_page);
        
        if (rq_data_dir(req) == WRITE)
            memcpy(dev->data + pos, buf + bvec.bv_offset, bvec.bv_len);
        else
            memcpy(buf + bvec.bv_offset, dev->data + pos, bvec.bv_len);
        
        kunmap_atomic(buf);
        pos += bvec.bv_len;
    }
    
    blk_mq_end_request(req, BLK_STS_OK);
    return BLK_STS_OK;
}

static const struct blk_mq_ops my_block_mq_ops = {
    .queue_rq = my_block_request,
};

/* Initialization */
static int my_block_init(void)
{
    struct my_block_dev *dev;
    int ret;
    
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev)
        return -ENOMEM;
    
    dev->size = 16 * 1024 * 1024;  /* 16 MB */
    dev->data = vmalloc(dev->size);
    if (!dev->data) {
        ret = -ENOMEM;
        goto out_free;
    }
    
    /* Setup tag set */
    dev->tag_set.ops = &my_block_mq_ops;
    dev->tag_set.nr_hw_queues = 1;
    dev->tag_set.queue_depth = 128;
    dev->tag_set.numa_node = NUMA_NO_NODE;
    dev->tag_set.flags = BLK_MQ_F_SHOULD_MERGE;
    
    ret = blk_mq_alloc_tag_set(&dev->tag_set);
    if (ret)
        goto out_vfree;
    
    /* Create disk */
    dev->disk = blk_mq_alloc_disk(&dev->tag_set, dev);
    if (IS_ERR(dev->disk)) {
        ret = PTR_ERR(dev->disk);
        goto out_tag_set;
    }
    
    dev->queue = dev->disk->queue;
    dev->queue->queuedata = dev;
    
    dev->disk->major = 0;  /* Dynamic */
    dev->disk->first_minor = 0;
    dev->disk->minors = 1;
    dev->disk->fops = &my_block_fops;
    dev->disk->private_data = dev;
    strcpy(dev->disk->disk_name, "myblock0");
    set_capacity(dev->disk, dev->size >> 9);  /* Size in sectors */
    
    ret = add_disk(dev->disk);
    if (ret)
        goto out_cleanup_disk;
    
    pr_info("Block device registered: %s\n", dev->disk->disk_name);
    return 0;

out_cleanup_disk:
    put_disk(dev->disk);
out_tag_set:
    blk_mq_free_tag_set(&dev->tag_set);
out_vfree:
    vfree(dev->data);
out_free:
    kfree(dev);
    return ret;
}

Chapter 10: Network Device Drivers

Overview

Network drivers handle packet transmission and reception. Use NAPI (New API) for efficient interrupt handling.

Basic Network Driver Structure

#include <linux/netdevice.h>
#include <linux/etherdevice.h>

struct my_net_priv {
    struct net_device *ndev;
    struct napi_struct napi;
    void __iomem *regs;
    struct sk_buff *tx_skb;
    spinlock_t lock;
};

/* Transmit packet */
static netdev_tx_t my_net_start_xmit(struct sk_buff *skb,
                                     struct net_device *ndev)
{
    struct my_net_priv *priv = netdev_priv(ndev);
    unsigned long flags;
    
    spin_lock_irqsave(&priv->lock, flags);
    
    /* Check if device is ready */
    if (priv->tx_skb) {
        netif_stop_queue(ndev);
        spin_unlock_irqrestore(&priv->lock, flags);
        return NETDEV_TX_BUSY;
    }
    
    priv->tx_skb = skb;
    
    /* Write packet to hardware */
    write_packet_to_hw(priv, skb->data, skb->len);
    
    ndev->stats.tx_packets++;
    ndev->stats.tx_bytes += skb->len;
    
    spin_unlock_irqrestore(&priv->lock, flags);
    
    return NETDEV_TX_OK;
}

/* NAPI poll function */
static int my_net_poll(struct napi_struct *napi, int budget)
{
    struct my_net_priv *priv = container_of(napi, struct my_net_priv, napi);
    struct net_device *ndev = priv->ndev;
    int work_done = 0;
    
    while (work_done < budget) {
        struct sk_buff *skb;
        int len;
        
        /* Check if packet available */
        if (!packet_available(priv))
            break;
        
        /* Allocate skb */
        skb = netdev_alloc_skb(ndev, MAX_PACKET_SIZE);
        if (!skb)
            break;
        
        /* Read packet from hardware */
        len = read_packet_from_hw(priv, skb->data, MAX_PACKET_SIZE);
        skb_put(skb, len);
        
        /* Set protocol */
        skb->protocol = eth_type_trans(skb, ndev);
        
        /* Pass to network stack */
        netif_receive_skb(skb);
        
        ndev->stats.rx_packets++;
        ndev->stats.rx_bytes += len;
        
        work_done++;
    }
    
    if (work_done < budget) {
        napi_complete_done(napi, work_done);
        /* Re-enable interrupts */
        enable_rx_interrupt(priv);
    }
    
    return work_done;
}

/* Interrupt handler */
static irqreturn_t my_net_interrupt(int irq, void *dev_id)
{
    struct net_device *ndev = dev_id;
    struct my_net_priv *priv = netdev_priv(ndev);
    u32 status;
    
    status = readl(priv->regs + REG_INT_STATUS);
    
    if (status & INT_RX) {
        /* Disable RX interrupt */
        disable_rx_interrupt(priv);
        /* Schedule NAPI */
        napi_schedule(&priv->napi);
    }
    
    if (status & INT_TX) {
        /* TX complete */
        if (priv->tx_skb) {
            dev_kfree_skb_irq(priv->tx_skb);
            priv->tx_skb = NULL;
            netif_wake_queue(ndev);
        }
    }
    
    return IRQ_HANDLED;
}

static const struct net_device_ops my_net_netdev_ops = {
    .ndo_open = my_net_open,
    .ndo_stop = my_net_stop,
    .ndo_start_xmit = my_net_start_xmit,
    .ndo_set_mac_address = eth_mac_addr,
    .ndo_validate_addr = eth_validate_addr,
};

/* Driver initialization */
static int my_net_probe(struct platform_device *pdev)
{
    struct net_device *ndev;
    struct my_net_priv *priv;
    int ret;
    
    /* Allocate net device */
    ndev = alloc_etherdev(sizeof(struct my_net_priv));
    if (!ndev)
        return -ENOMEM;
    
    priv = netdev_priv(ndev);
    priv->ndev = ndev;
    
    SET_NETDEV_DEV(ndev, &pdev->dev);
    platform_set_drvdata(pdev, ndev);
    
    /* Setup device */
    ndev->netdev_ops = &my_net_netdev_ops;
    ndev->ethtool_ops = &my_net_ethtool_ops;
    
    /* Initialize NAPI */
    netif_napi_add(ndev, &priv->napi, my_net_poll, 64);
    
    /* Get resources */
    priv->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(priv->regs)) {
        ret = PTR_ERR(priv->regs);
        goto err_free;
    }
    
    /* Request IRQ */
    ret = devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0),
                          my_net_interrupt, 0, dev_name(&pdev->dev), ndev);
    if (ret)
        goto err_free;
    
    /* Register network device */
    ret = register_netdev(ndev);
    if (ret)
        goto err_free;
    
    dev_info(&pdev->dev, "Network device registered\n");
    return 0;

err_free:
    free_netdev(ndev);
    return ret;
}

Chapter 11: Debugging Techniques

printk and Dynamic Debug

/* Logging levels */
pr_emerg("System unusable\n");
pr_alert("Action required\n");
pr_crit("Critical condition\n");
pr_err("Error condition\n");
pr_warn("Warning\n");
pr_notice("Normal but significant\n");
pr_info("Informational\n");
pr_debug("Debug message\n");  /* Requires DEBUG or dynamic debug */

/* Device-specific logging */
dev_err(&pdev->dev, "Device error\n");
dev_info(&pdev->dev, "Device info\n");
dev_dbg(&pdev->dev, "Device debug\n");  /* Dynamic debug */

/* Dynamic debug control */
// Enable: echo 'file my_driver.c +p' > /sys/kernel/debug/dynamic_debug/control
// Disable: echo 'file my_driver.c -p' > /sys/kernel/debug/dynamic_debug/control

Debugging Tools

1. /proc and /sys Debugging

/* Create debugfs entries */
#include <linux/debugfs.h>

static struct dentry *debug_dir;
static u32 debug_value;

static int create_debug_entries(void)
{
    debug_dir = debugfs_create_dir("my_driver", NULL);
    if (!debug_dir)
        return -ENOMEM;
    
    debugfs_create_u32("value", 0644, debug_dir, &debug_value);
    debugfs_create_file("stats", 0444, debug_dir, NULL, &stats_fops);
    
    return 0;
}

static void remove_debug_entries(void)
{
    debugfs_remove_recursive(debug_dir);
}

2. ftrace

# Enable function tracer
echo function > /sys/kernel/debug/tracing/current_tracer

# Trace specific function
echo my_driver_function > /sys/kernel/debug/tracing/set_ftrace_filter

# View trace
cat /sys/kernel/debug/tracing/trace

# Trace events
echo 1 > /sys/kernel/debug/tracing/events/my_driver/enable

3. Memory Debugging

/* KASAN (Kernel Address Sanitizer) - Enable in config */
CONFIG_KASAN=y

/* KMEMLEAK - Detect memory leaks */
CONFIG_DEBUG_KMEMLEAK=y

# Check for leaks
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak

/* SLUB debugging */
CONFIG_SLUB_DEBUG=y

# Enable SLUB debugging
slub_debug=FZP  /* Kernel parameter */

4. KGDB - Kernel Debugger

# Setup (requires two machines or VM)
# On target kernel command line:
kgdbwait kgdboc=ttyS0,115200

# On host with GDB:
gdb vmlinux
(gdb) target remote /dev/ttyS0
(gdb) break my_driver_probe
(gdb) continue

5. Crash Dumps

# Enable crash dumps
CONFIG_CRASH_DUMP=y

# After crash, analyze with crash utility:
crash vmlinux vmcore

6. Lockdep - Lock Validator

/* Enable in kernel config */
CONFIG_PROVE_LOCKING=y
CONFIG_DEBUG_LOCK_ALLOC=y

/* Lockdep will detect:
 * - Circular lock dependencies (potential deadlocks)
 * - Lock inversion
 * - Inconsistent lock state
 */

7. Common Debugging Patterns

/* Tracepoints */
#define CREATE_TRACE_POINTS
#include "my_driver_trace.h"

TRACE_EVENT(my_driver_event,
    TP_PROTO(int value),
    TP_ARGS(value),
    TP_STRUCT__entry(__field(int, value)),
    TP_fast_assign(__entry->value = value;),
    TP_printk("value=%d", __entry->value)
);

/* Use in code */
trace_my_driver_event(42);

/* BUG_ON and WARN_ON */
BUG_ON(ptr == NULL);        /* Panic kernel (use sparingly!) */
WARN_ON(condition);         /* Warning (preferred) */
WARN_ON_ONCE(condition);    /* Warn only once */

/* Assertions */
#ifdef DEBUG
#define assert(expr) \
    if (!(expr)) { \
        pr_err("Assertion failed: %s\n", #expr); \
        dump_stack(); \
    }
#else
#define assert(expr)
#endif

/* Stack trace */
#include <linux/stacktrace.h>
dump_stack();  /* Print stack trace */

Summary

This completes the advanced topics:

Block Drivers: Multi-queue block layer
Network Drivers: NAPI, packet handling
Debugging: printk, ftrace, KASAN, KGDB, lockdep

Essential Debugging Commands

# View kernel log
dmesg
journalctl -k

# Module info
lsmod
modinfo module_name

# Hardware info
lspci
lsusb

# Trace function calls
trace-cmd record -p function_graph -g my_function
trace-cmd report

# Performance profiling
perf record -g
perf report

Final Notes

Best Practices:

  1. Always test in VMs or test hardware
  2. Use managed resources (devm_*) when possible
  3. Follow kernel coding style
  4. Handle errors properly
  5. Document your code
  6. Test thoroughly before upstream submission

Resources:

End of advanced chapters!