69天探索操作系统-第46天:设备驱动程序

353 阅读6分钟

pro111.avif

1. 介绍

设备驱动程序是内核模块,用于实现硬件设备和操作系统之间的通信。它们充当硬件和操作系统之间的桥梁,使应用程序能够与硬件交互,而无需了解硬件实现的细节。

设备驱动程序中的关键概念

  • 内核模块架构: 设备驱动程序通常作为可加载的内核模块实现。
  • 设备类型: 设备可以分为字符设备、块设备和网络设备。
  • 驱动程序生命周期: 包括初始化、操作和清理阶段。
  • 硬件接口: 驱动程序通过内存映射I/O、中断和DMA与硬件交互。

image.png

2. 字符设备实现

字符设备作为字节流访问,类似于文件。下面是C语言实现的基本字符设备驱动程序:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#define DEVICE_NAME "chardev"
#define CLASS_NAME "char_class"

static struct class *char_class;
static struct cdev char_cdev;
static dev_t dev_num;

static int char_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "Character device opened\n");
    return 0;
}

static int char_release(struct inode *inode, struct file *file) {
    printk(KERN_INFO "Character device closed\n");
    return 0;
}

static ssize_t char_read(struct file *file,
                        char __user *buf,
                        size_t count,
                        loff_t *offset) {
    char data[] = "Hello from kernel\n";
    size_t datalen = strlen(data);

    if (*offset >= datalen)
        return 0;

    if (count > datalen - *offset)
        count = datalen - *offset;

    if (copy_to_user(buf, data + *offset, count))
        return -EFAULT;

    *offset += count;
    return count;
}

static ssize_t char_write(struct file *file,
                         const char __user *buf,
                         size_t count,
                         loff_t *offset) {
    char kernel_buf[1024];

    if (count > sizeof(kernel_buf))
        return -EINVAL;

    if (copy_from_user(kernel_buf, buf, count))
        return -EFAULT;

    printk(KERN_INFO "Received: %.*s\n", (int)count, kernel_buf);
    return count;
}

static struct file_operations fops = {
    .owner = THIS_MODULE,
    .open = char_open,
    .release = char_release,
    .read = char_read,
    .write = char_write
};

static int __init char_driver_init(void) {
    int ret;

    // Allocate device number
    ret = alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME);
    if (ret < 0) {
        printk(KERN_ALERT "Failed to allocate device number\n");
        return ret;
    }

    // Create device class
    char_class = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(char_class)) {
        unregister_chrdev_region(dev_num, 1);
        return PTR_ERR(char_class);
    }

    // Initialize and add character device
    cdev_init(&char_cdev, &fops);
    ret = cdev_add(&char_cdev, dev_num, 1);
    if (ret < 0) {
        class_destroy(char_class);
        unregister_chrdev_region(dev_num, 1);
        return ret;
    }

    // Create device file
    if (device_create(char_class, NULL, dev_num, NULL, DEVICE_NAME) == NULL) {
        cdev_del(&char_cdev);
        class_destroy(char_class);
        unregister_chrdev_region(dev_num, 1);
        return -EFAULT;
    }

    printk(KERN_INFO "Character device driver loaded\n");
    return 0;
}

static void __exit char_driver_exit(void) {
    device_destroy(char_class, dev_num);
    cdev_del(&char_cdev);
    class_destroy(char_class);
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Character device driver unloaded\n");
}

module_init(char_driver_init);
module_exit(char_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");

3. 设备注册和操作

设备注册涉及创建和管理设备上下文,这些上下文存储与设备相关的状态和资源。

设备上下文实现

以下是设备注册和管理的实现:

struct device_context {
    struct mutex lock;
    void __iomem *base_addr;
    int irq;
    struct work_struct work;
    wait_queue_head_t wait_queue;
    bool data_available;
};

static struct device_context *dev_ctx;

static long device_ioctl(struct file *file,
                        unsigned int cmd,
                        unsigned long arg) {
    struct device_context *ctx = file->private_data;
    int ret = 0;

    mutex_lock(&ctx->lock);

    switch (cmd) {
        case DEVICE_RESET:
            ret = reset_device(ctx);
            break;

        case DEVICE_GET_STATUS:
            ret = get_device_status(ctx, (void __user *)arg);
            break;

        case DEVICE_SET_CONFIG:
            ret = set_device_config(ctx, (void __user *)arg);
            break;

        default:
            ret = -ENOTTY;
    }

    mutex_unlock(&ctx->lock);
    return ret;
}

4. I/O 控制机制

I/O控制(ioctl)允许用户空间应用程序向设备驱动程序发送命令。以下是设备I/O控制的实现:

#define DEVICE_IOC_MAGIC 'k'
#define DEVICE_RESET _IO(DEVICE_IOC_MAGIC, 0)
#define DEVICE_GET_STATUS _IOR(DEVICE_IOC_MAGIC, 1, struct device_status)
#define DEVICE_SET_CONFIG _IOW(DEVICE_IOC_MAGIC, 2, struct device_config)

struct device_status {
    uint32_t state;
    uint32_t errors;
    uint64_t bytes_processed;
};

struct device_config {
    uint32_t mode;
    uint32_t timeout;
    uint32_t buffer_size;
};

static int reset_device(struct device_context *ctx) {
    void __iomem *base = ctx->base_addr;

    // Reset hardware registers
    writel(DEVICE_RESET_CMD, base + DEVICE_CTRL_REG);

    // Wait for reset completion
    if (!wait_for_completion_timeout(&ctx->reset_complete,
                                   msecs_to_jiffies(1000)))
        return -ETIMEDOUT;

    return 0;
}

5. 中断处理

中断处理对于及时响应硬件事件至关重要。以下是中断处理的一个实现:

static irqreturn_t device_isr(int irq, void *dev_id) {
    struct device_context *ctx = dev_id;
    uint32_t status;

    // Read interrupt status
    status = readl(ctx->base_addr + DEVICE_INT_STATUS_REG);

    if (!(status & DEVICE_INT_MASK))
        return IRQ_NONE;

    // Clear interrupt
    writel(status, ctx->base_addr + DEVICE_INT_CLEAR_REG);

    // Schedule bottom half
    schedule_work(&ctx->work);

    return IRQ_HANDLED;
}

static void device_work_handler(struct work_struct *work) {
    struct device_context *ctx =
        container_of(work, struct device_context, work);

    // Process interrupt data
    process_device_data(ctx);

    // Wake up waiting processes
    ctx->data_available = true;
    wake_up_interruptible(&ctx->wait_queue);
}
  1. 中断服务例程 (ISR): device_isr() 函数通过读取中断状态并调度工作队列来处理硬件中断。
  2. 工作队列处理器: device_work_handler() 函数处理中断数据并唤醒等待进程。

6. 内存管理

设备驱动程序中的内存管理涉及分配和管理DMA缓冲区以及映射设备寄存器。

DMA缓冲区实现

以下是DMA缓冲区管理的实现:

struct dma_buffer {
    void *cpu_addr;
    dma_addr_t dma_addr;
    size_t size;
};

static int allocate_dma_buffer(struct device *dev,
                             struct dma_buffer *buf,
                             size_t size) {
    buf->size = size;
    buf->cpu_addr = dma_alloc_coherent(dev, size, &buf->dma_addr, GFP_KERNEL);

    if (!buf->cpu_addr)
        return -ENOMEM;

    return 0;
}

static void free_dma_buffer(struct device *dev, struct dma_buffer *buf) {
    if (buf->cpu_addr) {
        dma_free_coherent(dev, buf->size, buf->cpu_addr, buf->dma_addr);
        buf->cpu_addr = NULL;
    }
}

static int setup_device_memory(struct device_context *ctx) {
    int ret;

    // Map device registers
    ctx->base_addr = ioremap(DEVICE_BASE_ADDR, DEVICE_REG_SIZE);
    if (!ctx->base_addr)
        return -ENOMEM;

    // Allocate DMA buffers
    ret = allocate_dma_buffer(ctx->dev, &ctx->rx_buffer, RX_BUFFER_SIZE);
    if (ret)
        goto err_unmap;

    ret = allocate_dma_buffer(ctx->dev, &ctx->tx_buffer, TX_BUFFER_SIZE);
    if (ret)
        goto err_free_rx;

    return 0;

err_free_rx:
    free_dma_buffer(ctx->dev, &ctx->rx_buffer);
err_unmap:
    iounmap(ctx->base_addr);
    return ret;
}
  1. DMA缓冲区结构: dma_buffer结构存储缓冲区的CPU和DMA地址。
  2. 缓冲区分配: allocate_dma_buffer()函数使用dma_alloc_coherent()分配一个DMA缓冲区。
  3. 缓冲区清理: free_dma_buffer()函数使用dma_free_coherent()释放一个DMA缓冲区。

7. 调试和测试

调试和测试对于确保设备驱动程序的可靠性至关重要。以下是调试设施的实现:

#define DRIVER_DEBUG 1

#if DRIVER_DEBUG
#define dev_dbg_reg(dev, reg, val) \
    dev_dbg(dev, "Register %s = 0x%08x\n", #reg, val)
#else
#define dev_dbg_reg(dev, reg, val) do {} while (0)
#endif

static void dump_registers(struct device_context *ctx) {
    uint32_t val;

    val = readl(ctx->base_addr + DEVICE_CTRL_REG);
    dev_dbg_reg(ctx->dev, DEVICE_CTRL_REG, val);

    val = readl(ctx->base_addr + DEVICE_STATUS_REG);
    dev_dbg_reg(ctx->dev, DEVICE_STATUS_REG, val);

    val = readl(ctx->base_addr + DEVICE_INT_MASK_REG);
    dev_dbg_reg(ctx->dev, DEVICE_INT_MASK_REG, val);
}

static int device_debugfs_init(struct device_context *ctx) {
    ctx->debugfs_dir = debugfs_create_dir(DEVICE_NAME, NULL);
    if (!ctx->debugfs_dir)
        return -ENOMEM;

    debugfs_create_file("registers", 0444, ctx->debugfs_dir,
                       ctx, &device_regs_fops);
    return 0;
}

8. 错误处理

错误处理确保驱动程序能够从意外情况中恢复。以下是错误处理的实现:

static int handle_device_error(struct device_context *ctx, int error) {
    dev_err(ctx->dev, "Device error: %d\n", error);

    // Log error details
    log_error_state(ctx);

    // Attempt recovery
    if (error == DEVICE_ERROR_TIMEOUT) {
        dev_warn(ctx->dev, "Attempting device reset\n");
        return reset_device(ctx);
    }

    if (error == DEVICE_ERROR_DMA) {
        dev_warn(ctx->dev, "Resetting DMA engine\n");
        return reset_dma(ctx);
    }

    // Critical error - disable device
    disable_device_interrupts(ctx);
    return -EIO;
}

static void log_error_state(struct device_context *ctx) {
    struct device_state state;

    // Capture device state
    get_device_state(ctx, &state);

    // Log to kernel ring buffer
    dev_err(ctx->dev, "Error state captured:\n");
    dev_err(ctx->dev, "Status: 0x%08x\n", state.status);
    dev_err(ctx->dev, "Control: 0x%08x\n", state.control);
    dev_err(ctx->dev, "Interrupt status: 0x%08x\n", state.int_status);

    // Store in device context for debugging
    memcpy(&ctx->last_error_state, &state, sizeof(state));
}

9. 结论

设备驱动程序开发需要对硬件接口和内核编程有深入的了解。正确实现字符设备、中断处理和内存管理对于可靠的驱动程序运行至关重要。