Linux PCI驱动编写

  这几天将以前为内核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就可以让普通用户有权限了

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值