首页 > 系统相关 >《Linux设备驱动开发详解(第3版)》 第18章 Linux驱动移植

《Linux设备驱动开发详解(第3版)》 第18章 Linux驱动移植

时间:2025-01-11 10:31:03浏览次数:3  
标签:count off 18 dev cdev Linux 驱动 my 设备

18.1 驱动移植概述

驱动移植是指将为某个特定硬件平台或内核版本编写的设备驱动程序,经过修改和适配,使其能够在另一个硬件平台或内核版本上正常运行。这一过程需要深入了解目标平台的硬件特性、内核架构以及驱动接口的差异。驱动移植通常涉及到硬件相关代码的调整、内核接口的更新以及编译和调试等步骤。

18.2 基于ARM平台的驱动移植实例

假设我们有一个简单的字符设备驱动,最初是为x86平台编写的,现在要将其移植到ARM平台。

原x86平台字符设备驱动代码(简化示例)
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

// 定义设备号
dev_t dev_num;
// 定义字符设备结构体
struct cdev my_cdev;
// 定义设备文件操作结构体
static struct file_operations my_fops = {
  .owner = THIS_MODULE,
  .read = my_read,
  .write = my_write,
  .open = my_open,
  .release = my_release,
};

// 定义一个缓冲区用于存储数据
char buffer[1024];

// 字符设备的读函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
    // filp: 文件结构体指针,代表当前打开的设备文件
    // buf: 用户空间缓冲区,用于接收数据
    // count: 用户期望读取的字节数
    // off: 文件偏移量

    size_t read_count = min(count, sizeof(buffer) - *off);
    if (copy_to_user(buf, buffer + *off, read_count)) {
        return -EFAULT;
    }
    *off += read_count;
    return read_count;
}

// 字符设备的写函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *off) {
    // filp: 文件结构体指针,代表当前打开的设备文件
    // buf: 用户空间缓冲区,包含要写入的数据
    // count: 用户期望写入的字节数
    // off: 文件偏移量

    size_t write_count = min(count, sizeof(buffer) - *off);
    if (copy_from_user(buffer + *off, buf, write_count)) {
        return -EFAULT;
    }
    *off += write_count;
    return write_count;
}

// 字符设备的打开函数
static int my_open(struct inode *inode, struct file *filp) {
    // inode: 索引节点结构体指针,代表设备文件的索引节点
    // filp: 文件结构体指针,代表当前打开的设备文件

    memset(buffer, 0, sizeof(buffer));
    return 0;
}

// 字符设备的释放函数
static int my_release(struct inode *inode, struct file *filp) {
    // inode: 索引节点结构体指针,代表设备文件的索引节点
    // filp: 文件结构体指针,代表当前打开的设备文件

    // 这里可以进行一些清理操作,例如关闭设备相关资源
    return 0;
}

// 模块初始化函数
static int __init char_dev_init(void) {
    int ret;
    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "my_char_dev");
    if (ret) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    // 初始化字符设备
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;

    // 添加字符设备到系统
    ret = cdev_add(&my_cdev, dev_num, 1);
    if (ret) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to add cdev\n");
        return ret;
    }

    printk(KERN_INFO "Character device registered successfully\n");
    return 0;
}

// 模块退出函数
static void __exit char_dev_exit(void) {
    // 从系统移除字符设备
    cdev_del(&my_cdev);
    // 释放设备号
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Character device unregistered successfully\n");
}

module_init(char_dev_init);
module_exit(char_dev_exit);
MODULE_LICENSE("GPL");
移植到ARM平台的修改
  1. 检查硬件差异:ARM平台可能有不同的寄存器地址、中断号等硬件相关信息。假设该字符设备驱动不涉及直接硬件访问,暂不需要针对硬件地址等进行修改。
  2. 编译选项调整:确保编译环境针对ARM平台进行设置。例如,在Makefile中修改交叉编译工具链路径。
  3. 内核接口兼容性:Linux内核在不同平台上的接口基本保持一致,但可能存在细微差异。这里暂未发现需要调整的内核接口。

经过上述检查,该驱动在ARM平台上理论上可以直接编译运行。实际情况中,如果涉及硬件访问,可能需要根据ARM平台的硬件手册调整相关代码。例如,如果设备使用特定的ARM中断控制器,需要修改中断申请和处理代码。

18.3 基于PowerPC平台的驱动移植实例

同样以这个字符设备驱动为例,将其移植到PowerPC平台。

硬件相关调整
  1. 地址对齐:PowerPC平台对内存地址对齐有特定要求。假设我们的缓冲区buffer在某些操作中需要特定对齐。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/mm.h> // 用于内存管理相关函数

// 定义设备号
dev_t dev_num;
// 定义字符设备结构体
struct cdev my_cdev;
// 定义设备文件操作结构体
static struct file_operations my_fops = {
  .owner = THIS_MODULE,
  .read = my_read,
  .write = my_write,
  .open = my_open,
  .release = my_release,
};

// 定义一个缓冲区用于存储数据,使用特定对齐方式分配内存
static char __aligned(64) buffer[1024];

// 字符设备的读函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
    // filp: 文件结构体指针,代表当前打开的设备文件
    // buf: 用户空间缓冲区,用于接收数据
    // count: 用户期望读取的字节数
    // off: 文件偏移量

    size_t read_count = min(count, sizeof(buffer) - *off);
    if (copy_to_user(buf, buffer + *off, read_count)) {
        return -EFAULT;
    }
    *off += read_count;
    return read_count;
}

// 字符设备的写函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *off) {
    // filp: 文件结构体指针,代表当前打开的设备文件
    // buf: 用户空间缓冲区,包含要写入的数据
    // count: 用户期望写入的字节数
    // off: 文件偏移量

    size_t write_count = min(count, sizeof(buffer) - *off);
    if (copy_from_user(buffer + *off, buf, write_count)) {
        return -EFAULT;
    }
    *off += write_count;
    return write_count;
}

// 字符设备的打开函数
static int my_open(struct inode *inode, struct file *filp) {
    // inode: 索引节点结构体指针,代表设备文件的索引节点
    // filp: 文件结构体指针,代表当前打开的设备文件

    memset(buffer, 0, sizeof(buffer));
    return 0;
}

// 字符设备的释放函数
static int my_release(struct inode *inode, struct file *filp) {
    // inode: 索引节点结构体指针,代表设备文件的索引节点
    // filp: 文件结构体指针,代表当前打开的设备文件

    // 这里可以进行一些清理操作,例如关闭设备相关资源
    return 0;
}

// 模块初始化函数
static int __init char_dev_init(void) {
    int ret;
    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "my_char_dev");
    if (ret) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    // 初始化字符设备
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;

    // 添加字符设备到系统
    ret = cdev_add(&my_cdev, dev_num, 1);
    if (ret) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to add cdev\n");
        return ret;
    }

    printk(KERN_INFO "Character device registered successfully\n");
    return 0;
}

// 模块退出函数
static void __exit char_dev_exit(void) {
    // 从系统移除字符设备
    cdev_del(&my_cdev);
    // 释放设备号
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Character device unregistered successfully\n");
}

module_init(char_dev_init);
module_exit(char_dev_exit);
MODULE_LICENSE("GPL");
  1. 中断处理:如果设备使用中断,PowerPC平台的中断控制器和中断处理机制与原平台不同。假设设备使用中断,以下是修改后的中断相关代码示例(假设中断号为MY_IRQ)。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/mm.h>
#include <linux/interrupt.h>

// 定义设备号
dev_t dev_num;
// 定义字符设备结构体
struct cdev my_cdev;
// 定义设备文件操作结构体
static struct file_operations my_fops = {
  .owner = THIS_MODULE,
  .read = my_read,
  .write = my_write,
  .open = my_open,
  .release = my_release,
};

// 定义一个缓冲区用于存储数据,使用特定对齐方式分配内存
static char __aligned(64) buffer[1024];

// 中断处理函数
irqreturn_t my_irq_handler(int irq, void *dev_id) {
    // irq: 中断号
    // dev_id: 设备标识

    // 这里处理中断相关操作,例如读取设备状态
    printk(KERN_INFO "Received interrupt %d\n", irq);
    return IRQ_HANDLED;
}

// 字符设备的读函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *off) {
    // filp: 文件结构体指针,代表当前打开的设备文件
    // buf: 用户空间缓冲区,用于接收数据
    // count: 用户期望读取的字节数
    // off: 文件偏移量

    size_t read_count = min(count, sizeof(buffer) - *off);
    if (copy_to_user(buf, buffer + *off, read_count)) {
        return -EFAULT;
    }
    *off += read_count;
    return read_count;
}

// 字符设备的写函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *off) {
    // filp: 文件结构体指针,代表当前打开的设备文件
    // buf: 用户空间缓冲区,包含要写入的数据
    // count: 用户期望写入的字节数
    // off: 文件偏移量

    size_t write_count = min(count, sizeof(buffer) - *off);
    if (copy_from_user(buffer + *off, buf, write_count)) {
        return -EFAULT;
    }
    *off += write_count;
    return write_count;
}

// 字符设备的打开函数
static int my_open(struct inode *inode, struct file *filp) {
    // inode: 索引节点结构体指针,代表设备文件的索引节点
    // filp: 文件结构体指针,代表当前打开的设备文件

    memset(buffer, 0, sizeof(buffer));
    // 申请中断
    if (request_irq(MY_IRQ, my_irq_handler, IRQF_SHARED, "my_char_dev_irq", this_module)) {
        printk(KERN_ERR "Failed to request IRQ\n");
        return -EFAULT;
    }
    return 0;
}

// 字符设备的释放函数
static int my_release(struct inode *inode, struct file *filp) {
    // inode: 索引节点结构体指针,代表设备文件的索引节点
    // filp: 文件结构体指针,代表当前打开的设备文件

    // 释放中断
    free_irq(MY_IRQ, this_module);
    // 这里可以进行一些清理操作,例如关闭设备相关资源
    return 0;
}

// 模块初始化函数
static int __init char_dev_init(void) {
    int ret;
    // 分配设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, "my_char_dev");
    if (ret) {
        printk(KERN_ERR "Failed to allocate device number\n");
        return ret;
    }

    // 初始化字符设备
    cdev_init(&my_cdev, &my_fops);
    my_cdev.owner = THIS_MODULE;

    // 添加字符设备到系统
    ret = cdev_add(&my_cdev, dev_num, 1);
    if (ret) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to add cdev\n");
        return ret;
    }

    printk(KERN_INFO "Character device registered successfully\n");
    return 0;
}

// 模块退出函数
static void __exit char_dev_exit(void) {
    // 从系统移除字符设备
    cdev_del(&my_cdev);
    // 释放设备号
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Character device unregistered successfully\n");
}

module_init(char_dev_init);
module_exit(char_dev_exit);
MODULE_LICENSE("GPL");
编译和调试
  1. 编译:确保使用PowerPC平台对应的交叉编译工具链进行编译。修改Makefile文件,指定正确的编译器路径和目标平台选项。
  2. 调试:使用PowerPC平台支持的调试工具,如gdb结合远程调试服务器(如果目标平台支持),对驱动进行调试。检查驱动加载过程中的打印信息,以及设备操作时的行为是否符合预期。

通过上述步骤,完成了字符设备驱动从原平台到PowerPC平台的移植。实际的驱动移植过程可能更加复杂,需要根据具体设备的功能和目标平台的特性进行详细的调整和优化。

标签:count,off,18,dev,cdev,Linux,驱动,my,设备
From: https://blog.csdn.net/qq_40844444/article/details/145024263

相关文章

  • 《Linux设备驱动开发详解(第3版)》 第9章 Linux内核定时器与工作队列
    9.1内核定时器内核定时器用于在指定的延迟时间后执行特定的函数。它在内核中常用于实现周期性任务或延迟执行的任务。#include<linux/module.h>#include<linux/kernel.h>#include<linux/timer.h>//定义一个内核定时器structtimer_listmy_timer;//定时器到......
  • 洛谷P2181 对角线
    对于一个 ......
  • Linux IPC:管道与FIFO汇总整理
    管道(Pipes)和先进先出(FIFOs,也称为命名管道)都是Linux中用于进程间通信(IPC)的机制。它们允许数据从一个进程流向另一个进程,类似于命令行中的管道操作符|。下面详细介绍这两种机制以及如何使用它们。管道(Pipes)管道是一种特殊的文件,它允许数据从一个进程(通常称为生产者)流向另......
  • wacom 手写笔linux驱动代码
    /*Wacomprotocol4serialtabletdriverThisprogramisfreesoftware;youcanredistributeitand/ormodifyitunderthetermsoftheGNUGeneralPublicLicenseaspublishedbytheFreeSoftwareFoundation;eitherversionof2oftheLicense,or(a......
  • Arch Linux Code-OSS里面空格显示太小了
    Code-OSS是linux版本的vscode,发现在ArchLinuxCode-OSS里面空格显示太小了,网上找了很多方法,有一个ubuntu用户说是第三个一定“第三个一定是monospace”,我无意中发现,其实就是字体的问题,只要你添加的字体足够多就行了,先看一下默认字体状态下的字体显示,就是一个tab确实是4个空......
  • 文件“硬连接”是 Linux 操作系统的缺陷吗,为啥跟微软的文件“软连接”,不一致?
    故事时间假设有个女孩叫 小文件:小文件在硬盘上有个家(inode),地址是2号楼304。这个家里存着她的全部信息:身高、体重(划掉)、兴趣爱好等等。硬链接:相当于【身份证】假如小文件要办两个身份证(硬链接),每个身份证都记录着:这个人住在2号楼304。无论用哪个身份证,都能找到本人只要......
  • Linux开机启动过程
    Linux系统的开机启动过程是一个复杂但有序的序列,它确保系统从硬件初始化到提供一个完全功能的操作环境。以下是这个过程的详细步骤:BIOS/UEFI启动:当计算机加电时,首先执行的是基本输入输出系统(BIOS)或更现代的统一可扩展固件接口(UEFI)。BIOS/UEFI主要负责硬件自检(POST,Power-O......
  • 在Linux中,如何进行系统性能的持续监控?
    在Linux中进行系统性能的持续监控,需要综合运用各种命令行工具和图形化界面工具,以及自动化脚本和第三方监控平台。以下是实现持续监控的一些建议步骤和工具:1.使用基础命令行工具实时查看top:实时查看CPU使用率、内存占用、运行中的进程等基本信息。htop(一个增强版的top):提供......
  • [Linux] 包管理器之【APT】
    序续:《[Linux]Linux中安装软件的方式?-博客园/千千寰宇》《[Linux]包管理器之综述【RPM/DPKG|YUM/APT】-博客园/千千寰宇》概述:包管理器APTAPT:AdvancedPackagingTool(现名)解释:AdvancedPackagingTool(apt),作为原始包管理器DPKG的前端包管理工具(在线包管......
  • 随笔:我为什么没有把《P5369 [PKUSC2018] 最大前缀和》做出来
    这是一篇随笔(绝对不是某CC风格的随笔)特别提醒:某W同学,再被【数据删除】要求写【数据删除】时你可以看一看这个大纲。我在干什么我在考【数据删除】时,开完题目后,我断定我就要解决这一道题。看见\(20\)这个小范围以后我就想起上一把【数据删除】的T【数据删除】。我就想DP......