Linux PCI驱动编写

news/2025/2/21 8:43:14/

  这几天将以前为内核2.6写的驱动移植到了4.1下,在这里记录一下过程,以及从头整理一下linux下pci驱动的编写方法。
  以前的驱动没有使用到linux下的probe方法,在4.1内核下成功编译后,一直无法进入中断,因此参考ch36x的驱动,重新写了驱动初始化部分,当应用层的程序可以调用驱动正确读回采集卡数据的时候,那份欣喜与满足感是难以言表的。

驱动模块初始化相关函数定义

  PCI驱动和其它linux驱动一样,需要定义init和exit两个函数作为加载模块的入口点和卸载模块的出口点,可以使用module_pci_driver宏,只要将pci_driver结构体变量作为参数调用这个宏,就会自动定义init和exit这两个函数,好处是代码整洁,缺点就是不够灵活,我这里仍然用传统的方法使用module_init和module_exit两个宏:

module_init( init_m );
module_exit( cleanup_m );

  init_m是使用insmod命令加载驱动模块时调用的函数,cleanup_m是用rmmod卸载模块时调用的函数。
  在init_m函数中先调用alloc_chrdev_region来给设备分配编号,然后调用class_create函数创建一个设备类,只有调用这个函数后才能够使用device_create函数在/dev目录下动态创建设备节点(在probe函数中调用),最后调用pci_register_driver宏把pci专用的结构体pci_driver变量放到pci总线设备组中,让内核知道有我们这个驱动模块的存在。

alloc_chrdev_region函数说明如下:
函数原型:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

参数说明:
dev:向内核申请下来的设备号
baseminor :次设备号的起始
count: 申请次设备号的个数
name :执行 cat /proc/devices显示的名称,由驱动编写者定义
返回值:执行成功返回0

class_create函数说明如下:
函数原型:

struct class *class_create(struct module *owner, const char *name);

参数说明:
owner:指定类的所有者是哪个模块,可以用THIS_MODULE获取本驱动模块;
name:类名,由驱动编写者定义。
返回值:创建的类指针。
可以用IS_ERR判断返回的类指针是否有错误,如果有错误,则使用PTR_ERR来返回错误代码。
在cleanup_m函数中注销掉pci驱动结构体以及之前申请到的设备号。

pci_driver结构体:

这个结构体的声明在include/linux/pci.h里面:

struct pci_driver {
struct list_head node;
char *name;
const struct pci_device_id *id_table;
int   (*probe)   (struct pci_dev *dev, const struct pci_device_id *id);
void (*remove) (struct pci_dev *dev);
int   (*save_state) (struct pci_dev *dev, u32 state);
int   (*suspend)(struct pci_dev *dev, u32 state);
int   (*resume) (struct pci_dev *dev);
int   (*enable_wake) (struct pci_dev *dev, u32 state, int enable);
};

我的驱动中定义的有如下几项:
name:驱动模块名称
id_table:PCI设备的厂商ID,设备ID等
probe:总线发现我们设备的时候调用的函数,对于已经插好的PCI板卡,加载驱动模块的时候就会调用此函数。
remove:移除设备的时候调用,在卸载驱动模块的时候也会调用。

probe函数

  在这个函数中要为设备的私有数据结构变量分配空间,使能设备,获取设备所需的资源,注册中断等,其中涉及到的相关函数介绍如下:
pci_enable_device:使能设备的IO和内存空间,如果设备处于挂起状态则唤醒之。
  pci_request_regions:通知内核设备IO和内存空间已经被指定名称的设备占用,其它就不要再占用了。
  pci_set_drvdata:设置PCI驱动私有数据,本来这个函数将私有数据结构变量和pci_dev关联起来了,但是在file_operations相关操作中没有pci_dev获取方法,所以我最后极不优雅的使用全局变量来记录设备私有数据。
  request_irq:注册中断
  cdev_init:初始化字符设备的cdev变量
  cdev_add:添加一个字符设备到系统中
  device_create:创建字符设备,这个函数调用完成后,在系统的/dev目录下将会有函数设定的设备文件出现。

remove函数

  此函数清理了probe函数中所申请的资源,注意清理资源时要和申请资源的顺序相反。

驱动模块文件操作相关函数

  之前的工作是将驱动模块加载到内核中,并且为相关设备分配资源。接下来要想应用层访问到设备,操控设备,则需要进行一些文件操作。对于应用程序来讲,一个设备也就是一个文件。

file_operations结构体

  这个结构体的声明在include/linux/fs.h文件中

struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);int (*setlease)(struct file *, long, struct file_lock **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);int (*show_fdinfo)(struct seq_file *m, struct file *f);
};

  结构体成员函数很多,但是我的驱动只实现了open,release,unlocked_ioctl,read。下面一一介绍。

open函数

  应用层调用open函数时,驱动模块的open函数就会被调用(有点废话的感觉),驱动模块的open函数会传递进两个参数:
  inode结构和file结构,inode结构对于我们比较有用的就是获取设备索引,获得设备索引后就可以填充file结构中的private_data成员变量,这个成员变量实际上就是前面probe函数中定义的私有数据成员变量,在我的程序里,我用一个全局指针数组来存放所有设备的私有数据成员指针。伪代码如下:

int zt_pl2360_open(struct inode *inode,struct file *filp)
{int nRet = 0;PZT_PL2360_Dev pd = 0; int nInx = MINOR( inode->i_rdev );pd = g_pZT_PL2360_MyDev[nInx];filp->private_data = pd;return nRet;
}

release函数在应用层调用close函数时会被调用,它是open的反向操作,就不多说了。
read函数将内核数据复制到应用层,我这里就是将采集卡读出的数据交给应用程序。

unlocked_ioctl函数

  这个函数在低版本内核中就是ioctl,对应应用层的ioctl调用,在kernel 2.6.36后使用unlocked_ioctl和compat_ioctl代替,其中unlocked_ioctl在应用层和内核层相同位数的时候调用,compat_ioctl在32位应用,64位内核驱动的时候调用。
  在我的驱动中,这个函数是应用程序与设备通讯的主要函数,设置设备参数,读回设备状态等操作均在这个函数中完成。伪代码如下:

long zt_pl2360_ioctl(struct file *filp,unsigned int cmd, unsigned long arg)
{int retval = 0;PZT_PL2360_Dev pd = (PZT_PL2360_Dev)(filp->private_data);if (_IOC_TYPE(cmd) != PL2360_IOC_MAGIC) return -ENOTTY;if (_IOC_NR(cmd) > PL2360_IOC_MAXNR) return -ENOTTY;switch(cmd) {case PL2360_IOC_OUTL:retval = PL2360_IOC_Outl( pd, arg );break;case PL2360_IOC_INL:retval = PL2360_IOC_Inl( pd, arg );break;……default:  return -ENOTTY;}return retval;		
}

  最后在说一点,驱动完成后,我安装的时候遇到了“-1 Unknown symbol in module”错误,需要在文件添加:MODULE_LICENSE(“GPL”);问题解决。

修改设备节点权限,让非root用户可以使用

首先看你节点信息:
udevadm info --attribute-walk --name=/dev/pl21080
/dev/pl21080是节点名称
我的信息:
looking at device ‘/devices/pci0000:00/0000:00:1c.1/0000:02:00.0/0000:03:0c.0/pl2108_class/pl21080’:
KERNEL==“pl21080”
SUBSYSTEM==“pl2108_class”
DRIVER==“”
然后在/etc/udev/rules.d目录下新建文件
vi 10-myrule.rules
添加
SUBSYSTEM==“pl2108_class”, MODE=“0666”
保存退出
此时再insmod就可以让普通用户有权限了


http://www.ppmy.cn/news/274634.html

相关文章

Linux的PCI驱动介绍(入门)

1. 关键数据结构 PCI设备上有三种地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。CPU可以访问PCI设备上的所有地址空间,其中I/O空间和存储空间提供给设备驱动程序使用,而配置空间则由Linux内核中的PCI初始化代码使用。内核在启动时负…

pci驱动开发详解

一、在了解pic启动开发前,作为开发人员需了解以下内核结构体: struct pci_device_id { __u32 vendor, device;/* Vendor and device ID or PCI_ANY_ID*/ __u32 subvendor, subdevice;/* Subsystem IDs or PCI_ANY_ID */ __u32 class, class_…

pci

PCI学习笔记 (2011-01-25 12:18) 标签: PCI 配置空间 枚举 分类: PowerPC架构 1.PCI设备编号 每一个PCI device都有其unique PFA(PCI Fcntion Address) PFA由 bus number、device number、function number组成。 一条PCI总线支持256个PFA&…

pcie驱动介绍

转载: pcie驱动介绍_fight_onlyfor_you的博客-CSDN博客_pcie驱动 PCIE设备的地址由总线号、设备号和功能号组成,分别称为厂家ID、设备ID和设备类代码 我们可以利用lspci工具了解这些概念。PCI工具集的一部分,下载地址为http://mj.ucw.cz…

FeignClient【问题】Method threw ‘feign.codec.DecodeException‘ exception.也许是最简单的解决方法

1.问题详情 Method threw feign.codec.DecodeException exception. # detail Could not extract response: no suitable HttpMessageConverter found for response type [class java.lang.Object] and content type [text/json;charsetUTF-8]Openfeign版本&#xff1a; <…

动态测试数据处理

分类 动态测试数据&#xff1a; 1、确定性数据&#xff1a;能够用明确的数学表达式进行描述的数据称为确定性数据。 Ⅰ、周期数据 Ⅱ、非周期数据 2、随机性数据&#xff1a;无法用明确的数学表达式表述&#xff1b;若在一个…

支付系统设计

支付永远是一个公司的核心领域&#xff0c;因为这是一个有交易属性公司的命脉。那么&#xff0c;支付系统到底长什么样&#xff0c;又是怎么运行交互的呢? 抛开带有支付牌照的金融公司的支付架构&#xff0c;下述链路和系统组成基本上符合绝大多数支付场景。 其实整体可以看成…

Servlet、JSP

一、web服务器 1.1 常用服务器产品 Tomcat&#xff08;Apache开源&#xff0c;主流的web服务器之一&#xff0c;多用于javaweb项目开发&#xff09; jetty&#xff08;效率高于Tomcat&#xff09; WebLogic&#xff08;Oracl 收费&#xff09; WebSpera&#xff08;IBM&#xf…