首页 > 系统相关 >《Linux设备驱动开发详解(第3版)》 第17章 Linux设备驱动开发综合实例

《Linux设备驱动开发详解(第3版)》 第17章 Linux设备驱动开发综合实例

时间:2025-01-11 10:31:18浏览次数:3  
标签:struct 17 dev static Linux device 驱动 my 设备

17.1 开发板硬件介绍

假设我们使用的是一款基于ARM架构的开发板,板上集成了多种设备,如串口、GPIO、SPI、I2C等接口,以及网卡、USB控制器、PCI插槽等设备。不同的开发板硬件特性会有所不同,这里以通用的硬件配置为例进行说明。

17.2 字符设备驱动开发实例

字符设备是一种以字节流方式进行数据传输的设备,例如串口设备。下面是一个简单的字符设备驱动实例:

#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");

17.3 块设备驱动开发实例

块设备以块为单位进行数据传输,通常支持随机访问,如硬盘设备。以下是一个简单的块设备驱动示例:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/bio.h>

// 定义设备号
dev_t dev_num;
// 定义块设备结构体
struct gendisk *my_disk;
// 定义请求队列
struct request_queue *my_queue;

// 块设备的请求处理函数
static void my_request_fn(struct request_queue *q) {
    struct request *req;
    // 循环处理请求队列中的请求
    while ((req = elv_next_request(q))!= NULL) {
        struct bio *bio;
        // 遍历请求中的所有bio
        for_each_bio(bio, req) {
            sector_t sector = bio->bi_sector;
            char *buffer = bio->bi_io_vec[0].bv_data;
            unsigned int len = bio->bi_io_vec[0].bv_len;
            // 这里模拟实际的I/O操作,例如根据扇区编号填充数据
            for (unsigned int i = 0; i < len; i++) {
                buffer[i] = sector + i;
            }
            // 标记bio完成
            bio_endio(bio, 0);
        }
        // 标记请求完成
        end_request(req, 0);
    }
}

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

    // 创建请求队列
    my_queue = blk_init_queue(my_request_fn, THIS_MODULE);
    if (!my_queue) {
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to create request queue\n");
        return -ENOMEM;
    }

    // 创建gendisk结构体
    my_disk = alloc_disk(16); // 假设设备有16个分区
    if (!my_disk) {
        blk_cleanup_queue(my_queue);
        unregister_chrdev_region(dev_num, 1);
        printk(KERN_ERR "Failed to allocate gendisk\n");
        return -ENOMEM;
    }

    // 设置gendisk的主设备号和次设备号
    my_disk->major = MAJOR(dev_num);
    my_disk->first_minor = MINOR(dev_num);
    // 设置gendisk的名称
    sprintf(my_disk->disk_name, "my_disk");
    // 设置gendisk的所有者为当前模块
    my_disk->owner = THIS_MODULE;
    // 设置gendisk的容量,这里假设为1024个扇区,每个扇区512字节
    my_disk->capacity = 1024 * 512;
    // 设置gendisk的请求队列
    my_disk->queue = my_queue;

    // 注册gendisk
    add_disk(my_disk);

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

// 模块退出函数
static void __exit block_dev_exit(void) {
    // 从系统移除gendisk
    del_gendisk(my_disk);
    // 释放gendisk结构体
    put_disk(my_disk);
    // 清理请求队列
    blk_cleanup_queue(my_queue);
    // 释放设备号
    unregister_chrdev_region(dev_num, 1);
    printk(KERN_INFO "Block device unregistered successfully\n");
}

module_init(block_dev_init);
module_exit(block_dev_exit);
MODULE_LICENSE("GPL");

17.4 网络设备驱动开发实例

网络设备驱动负责控制网络硬件设备,实现网络数据的发送和接收。以下是一个简单的网络设备驱动示例:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/skbuff.h>

// 定义网络设备结构体指针
struct net_device *my_netdev;

// 网络设备的发送函数
static netdev_tx_t my_netdev_xmit(struct sk_buff *skb, struct net_device *dev) {
    // skb: 包含要发送数据的套接字缓冲区
    // dev: 网络设备结构体指针

    // 这里模拟实际的硬件发送操作,例如将skb数据发送到网络硬件
    printk(KERN_INFO "Sending packet of length %zu\n", skb->len);

    // 发送完成后释放套接字缓冲区
    dev_kfree_skb(skb);

    // 返回发送成功的标志
    return NETDEV_TX_OK;
}

// 网络设备的接收函数(假设通过中断触发接收)
static void my_netdev_rx_interrupt(struct net_device *dev) {
    // 这里模拟从硬件接收数据并创建套接字缓冲区
    struct sk_buff *skb = dev_alloc_skb(2048);
    if (!skb) {
        printk(KERN_ERR "Failed to allocate skb for receiving\n");
        return;
    }

    // 假设从硬件读取数据填充到skb中
    // 这里省略实际的硬件读取操作

    // 将接收到的数据传递给上层协议栈
    netif_rx(skb);
}

// 网络设备的打开函数
static int my_netdev_open(struct net_device *dev) {
    // 这里可以进行硬件相关的初始化操作,例如启动设备时钟
    printk(KERN_INFO "Network device %s opened\n", dev->name);
    return 0;
}

// 网络设备的关闭函数
static int my_netdev_close(struct net_device *dev) {
    // 这里可以进行硬件相关的关闭操作,例如停止设备时钟
    printk(KERN_INFO "Network device %s closed\n", dev->name);
    return 0;
}

// 模块初始化函数
static int __init net_dev_init(void) {
    my_netdev = alloc_netdev(0, "my_netdev", ether_setup);
    if (!my_netdev) {
        printk(KERN_ERR "Failed to allocate network device\n");
        return -ENOMEM;
    }

    // 设置网络设备的打开和关闭回调函数
    my_netdev->open = my_netdev_open;
    my_netdev->stop = my_netdev_close;

    // 设置网络设备的发送回调函数
    my_netdev->hard_start_xmit = my_netdev_xmit;

    // 设置网络设备的MAC地址,这里简单示例设置为全0
    unsigned char mac_addr[ETH_ALEN] = {0, 0, 0, 0, 0, 0};
    memcpy(my_netdev->dev_addr, mac_addr, ETH_ALEN);

    // 注册网络设备
    if (register_netdev(my_netdev)) {
        free_netdev(my_netdev);
        printk(KERN_ERR "Failed to register network device\n");
        return -ENODEV;
    }

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

// 模块退出函数
static void __exit net_dev_exit(void) {
    // 注销网络设备
    unregister_netdev(my_netdev);
    // 释放网络设备结构体
    free_netdev(my_netdev);
    printk(KERN_INFO "Network device unregistered successfully\n");
}

module_init(net_dev_init);
module_exit(net_dev_exit);
MODULE_LICENSE("GPL");

17.5 USB设备驱动开发实例

USB设备驱动用于控制USB设备,实现设备的枚举、数据传输等功能。以下是一个简单的USB设备驱动示例:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/usb.h>

// 定义USB设备驱动结构体
static struct usb_driver my_usb_driver = {
.name = "my_usb_device",
.probe = my_probe,
.disconnect = my_disconnect,
.id_table = my_usb_ids,
};

// USB设备ID表,用于匹配设备
static struct usb_device_id my_usb_ids[] = {
    {
    .match_flags = USB_DEVICE_ID_VENDOR_AND_PRODUCT,
    .idVendor = 0x1234, // 假设的厂商ID
    .idProduct = 0x5678, // 假设的产品ID
    },
    {}
};

// 批量传输的缓冲区
char bulk_buffer[256];

// 批量传输的端点地址
static unsigned char bulk_in_endpoint;
static unsigned char bulk_out_endpoint;

// 当USB设备插入时调用的probe函数
static int my_probe(struct usb_interface *intf, const struct usb_device_id *id) {
    struct usb_device *dev = interface_to_usbdev(intf);
    struct usb_host_interface *interface;
    struct usb_endpoint_descriptor *endpoint;
    int i;

    printk(KERN_INFO "My USB device inserted: vendor 0x%04x product 0x%04x\n", id->idVendor, id->idProduct);

    interface = intf->cur_altsetting;

    for (i = 0; i < interface->desc.bNumEndpoints; i++) {
        endpoint = &interface->endpoint[i].desc;
        if (usb_endpoint_is_bulk_in(endpoint)) {
            bulk_in_endpoint = endpoint->bEndpointAddress;
        } else if (usb_endpoint_is_bulk_out(endpoint)) {
            bulk_out_endpoint = endpoint->bEndpointAddress;
        }
    }

    // 这里可以进行其他初始化操作

    return 0;
}

// 当USB设备拔出时调用的disconnect函数
static void my_disconnect(struct usb_interface *intf) {
    printk(KERN_INFO "My USB device removed\n");
}

// 执行批量写操作的函数
static int my_bulk_write(struct usb_device *dev, const char *data, int len) {
    int ret;
    ret = usb_bulk_msg(dev, bulk_out_endpoint, (void *)data, len, NULL, 1000);
    if (ret < 0) {
        printk(KERN_ERR "Bulk write failed: %d\n", ret);
    }
    return ret;
}

// 执行批量读操作的函数
static int my_bulk_read(struct usb_device *dev, char *data, int len) {
    int ret;
    ret = usb_bulk_msg(dev, bulk_in_endpoint, data, len, NULL, 1000);
    if (ret < 0) {
        printk(KERN_ERR "Bulk read failed: %d\n", ret);
    }
    return ret;
}

// 模块初始化函数
static int __init usb_dev_init(void) {
    // 注册USB设备驱动
    if (usb_register(&my_usb_driver)) {
        printk(KERN_ERR "Failed to register USB driver\n");
        return -1;
    }
    printk(KERN_INFO "USB driver registered successfully\n");
    return 0;
}

// 模块退出函数
static void __exit usb_dev_exit(void) {
    // 注销USB设备驱动
    usb_deregister(&my_usb_driver);
    printk(KERN_INFO "USB driver unregistered successfully\n");
}

module_init(usb_dev_init);
module_exit(usb_dev_exit);
MODULE_LICENSE("GPL");

17.6 PCI设备驱动开发实例

PCI设备驱动用于控制PCI设备,实现设备的枚举、配置空间访问等功能。以下是一个简单的PCI设备驱动示例:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>

// 定义PCI设备驱动结构体
static struct pci_driver my_pci_driver = {
 .name = "my_pci_device",
 .probe = my_probe,
 .remove = my_remove,
 .id_table = my_pci_ids,
};

// PCI设备ID表,用于匹配设备
static struct pci_device_id my_pci_ids[] = {
    {
     .vendor = 0x1234,
     .device = 0x5678,
    },
    {}
};

// 当PCI设备插入时调用的probe函数
static int my_probe(struct pci_dev *pdev, const struct pci_device_id *id) {
    u16 vendor_id, device_id;
    u3

标签:struct,17,dev,static,Linux,device,驱动,my,设备
From: https://blog.csdn.net/qq_40844444/article/details/145024201

相关文章

  • 《Linux设备驱动开发详解(第3版)》 第18章 Linux驱动移植
    18.1驱动移植概述驱动移植是指将为某个特定硬件平台或内核版本编写的设备驱动程序,经过修改和适配,使其能够在另一个硬件平台或内核版本上正常运行。这一过程需要深入了解目标平台的硬件特性、内核架构以及驱动接口的差异。驱动移植通常涉及到硬件相关代码的调整、内核接口......
  • 《Linux设备驱动开发详解(第3版)》 第9章 Linux内核定时器与工作队列
    9.1内核定时器内核定时器用于在指定的延迟时间后执行特定的函数。它在内核中常用于实现周期性任务或延迟执行的任务。#include<linux/module.h>#include<linux/kernel.h>#include<linux/timer.h>//定义一个内核定时器structtimer_listmy_timer;//定时器到......
  • 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的前端包管理工具(在线包管......
  • P1792 [国家集训队] 种树
    题意:给一个长度为n的环形数组,你要选m个数,满足没有任意两个数的位置相邻,求总和最大。一开始没仔细看数据范围写了个dp暴力,想着枚举第1个点选还是不选两次dp取最大值。(属于痴心妄想)后面自己也是看的题解。我们先贪心选最大的,那么它两边就不可以选了,但有可能选两边比选这个更好,那......